Files
frontend/docs/auth-service-refactoring-guide.md
T

288 lines
7.3 KiB
Markdown

# 서비스별 인증 처리 리팩토링 가이드
## 1) 목적과 원칙
여러 서비스 앱에 흩어진 인증 로직을 `@common/auth` 기반으로 통일하기 위한 표준 가이드입니다.
이 문서를 **위에서 아래로** 적용하면 `authService` + `AuthWrapper` 패턴이 갖춰집니다.
핵심 원칙:
- 인증 **정책·유틸**은 `@common/auth`에 모은다.
- 서비스별 차이는 `authConfig` 설정으로만 표현한다.
- 앱은 API 오케스트레이션(`authService`)과 진입점(`AuthWrapper`)만 구현한다.
- `login` / `silentRefresh` 성공 시 토큰을 저장한다.
- 세션 연장은 confirm **확인** 시 `silentRefresh`로 수행한다.
→ [세션 연장 Confirm 적용 가이드](./session-extend-confirm-guide.md)
참고 구현: `apps/as/src/entities/auth/api/auth-service.ts`
---
## 2) 목표 아키텍처
### 2-1. 레이어 책임
| 레이어 | 경로 | 역할 |
| --- | --- | --- |
| 환경 변수 | `shared/config/env.ts` | `CLAIM_NAME` 등 키/도메인 값 노출 |
| 인증 설정 | `shared/config/auth.ts` | env → `AuthConfig` 매핑 |
| 토큰 매니저 | `entities/auth/lib/token-manager.ts` | `createTokenManager(authConfig)` + re-export |
| 인증 서비스 | `entities/auth/api/auth-service.ts` | login / silentRefresh / declineSessionExtend / logout |
| 인증 래퍼 | `entities/auth/lib/auth-wrapper.tsx` | auth 체크, `auth:logout` 수신 |
| 배럴 | `entities/auth/index.ts` | 외부 import 경로 통일 |
| 레이아웃 | `BaseLayout` 등 | `authService`를 Header까지 전달 |
### 2-2. 런타임 흐름
```mermaid
flowchart TD
A[login / silentRefresh 성공] --> B[setToken]
C[세션 연장 confirm 확인] --> D[silentRefresh]
D --> B
E[세션 연장 confirm 취소] --> F[declineSessionExtend]
F --> G[clearRefreshTimer]
H[갱신 실패 / logout] --> I[clearAuthToken + auth:logout]
```
세션 연장 confirm UX는 [세션 연장 가이드](./session-extend-confirm-guide.md)에서 적용한다.
---
## 3) 공통 계약
### 3-1. AuthConfig
```ts
interface AuthConfig {
authUserId: string;
claimName: string;
cookieDomain?: string;
accessToken?: string;
refreshToken?: string;
}
```
### 3-2. AuthService
```ts
interface AuthService {
login(payload: AuthPayload): Promise<void>;
silentRefresh(): Promise<void>;
declineSessionExtend(): void;
logout(): Promise<void>;
}
```
### 3-3. TokenManager API
앱에서 사용하는 메서드:
| 메서드 | 용도 |
| --- | --- |
| `setToken` / `getToken` | 쿠키 저장·조회 |
| `clearRefreshTimer` | `declineSessionExtend`에 연결 |
| `clearAuthToken` | 인증 쿠키 일괄 삭제 |
---
## 4) 적용 순서
의존 관계 순으로 진행한다.
### 4-1. 환경 변수 통일
파일: `shared/config/env.ts`
```ts
export const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization";
export const AUTH_USER_ID = process.env.NEXT_PUBLIC_AUTH_USER_ID || "token-id";
export const ACCESS_TOKEN =
process.env.NEXT_PUBLIC_ACCESS_TOKEN || "access-token";
export const REFRESH_TOKEN =
process.env.NEXT_PUBLIC_REFRESH_TOKEN || "refresh-token";
export const COOKIE_DOMAIN = process.env.NEXT_PUBLIC_COOKIE_DOMAIN;
```
### 4-2. 인증 설정
파일: `shared/config/auth.ts`
```ts
import type { AuthConfig } from "@common/auth";
import {
ACCESS_TOKEN,
AUTH_USER_ID,
CLAIM_NAME,
COOKIE_DOMAIN,
REFRESH_TOKEN,
} from "./env";
export const authConfig: AuthConfig = {
authUserId: AUTH_USER_ID,
claimName: CLAIM_NAME,
cookieDomain: COOKIE_DOMAIN,
accessToken: ACCESS_TOKEN,
refreshToken: REFRESH_TOKEN,
};
```
### 4-3. 토큰 매니저
파일: `entities/auth/lib/token-manager.ts`
```ts
import { authConfig } from "@/shared/config/auth";
import { createTokenManager } from "@common/auth";
export const tokenManager = createTokenManager(authConfig);
export const {
getToken,
setToken,
removeToken,
clearRefreshTimer,
getAccessToken,
getRefreshToken,
getUserId,
} = tokenManager;
```
### 4-4. 인증 서비스
파일: `entities/auth/api/auth-service.ts`
참고: `apps/as/src/entities/auth/api/auth-service.ts`
#### login
```ts
setToken(CLAIM_NAME, accessToken);
setToken(AUTH_USER_ID, tokenId);
```
#### silentRefresh
```ts
async function silentRefresh() {
try {
// SILENT_REFRESH API → setToken(...)
} catch (error) {
// clearAuthToken은 내부에서 this를 쓰므로 tokenManager 객체로 호출
tokenManager.clearAuthToken();
if (typeof window !== "undefined") {
window.dispatchEvent(new CustomEvent("auth:logout"));
}
}
}
```
#### declineSessionExtend
```ts
declineSessionExtend: clearRefreshTimer,
```
| 사용자 선택 | 호출 |
| --- | --- |
| confirm **확인** | `silentRefresh()` |
| confirm **취소** | `declineSessionExtend()` |
#### logout
```ts
async function logout() {
tokenManager.clearAuthToken();
clearDeviceId();
if (typeof window !== "undefined") {
window.dispatchEvent(new CustomEvent("auth:logout"));
}
}
```
httpOnly refresh 쿠키 삭제가 필요하면 `fetch("/api/auth/logout")`를 먼저 호출한다 (`apps/st` 참고).
#### export
```ts
export const authService: AuthService = {
login,
silentRefresh,
declineSessionExtend: clearRefreshTimer,
logout,
};
```
### 4-5. AuthWrapper
파일: `entities/auth/lib/auth-wrapper.tsx`
인증 체크와 `auth:logout` 수신을 구현한다.
```tsx
useEffect(() => {
// authCheck, 로그인 리다이렉트
}, []);
useEffect(() => {
function handleAuthLogout() {
setUserInfo(null);
if (!isAtLoginRedirectUrl(AUTH_LOGIN_REDIRECT_URL)) {
router.replace(AUTH_LOGIN_REDIRECT_URL);
}
}
window.addEventListener("auth:logout", handleAuthLogout);
return () => window.removeEventListener("auth:logout", handleAuthLogout);
}, []);
```
### 4-6. 배럴
파일: `entities/auth/index.ts`
```ts
export { authService } from "./api/auth-service";
export { AuthWrapper } from "./lib/auth-wrapper";
export { tokenManager } from "./lib/token-manager";
```
### 4-7. 레이아웃·Provider 연결
```tsx
import { authService } from "@/entities/auth";
<BaseLayout authService={authService} /* ... */>
{children}
</BaseLayout>
```
`AppProvider`에 `AuthWrapper`를 마운트한다.
---
## 5) 적용 체크리스트
- [ ] `env.ts`에 인증 관련 환경 변수가 정의되어 있다
- [ ] `authConfig`가 `AuthConfig`로 매핑되어 있다
- [ ] `createTokenManager(authConfig)`로 tokenManager를 생성한다
- [ ] `authService`가 `AuthService` 네 메서드를 구현한다
- [ ] `entities/auth` 배럴로 외부 참조가 통일되어 있다
- [ ] `BaseLayout`에 `authService`가 주입된다
- [ ] `AppProvider`에 `AuthWrapper`가 마운트되어 있다
---
## 6) 후속 단계
위 체크리스트 통과 후 [세션 연장 Confirm 적용 가이드](./session-extend-confirm-guide.md)를 진행한다.
---
## 7) 참고 구현
| 앱 | auth-service |
| --- | --- |
| as (기준) | `apps/as/src/entities/auth/api/auth-service.ts` |
| cs | `apps/cs/src/entities/auth/api/auth-service.ts` |
| st | `apps/st/src/entities/auth/api/auth-service.ts` |
| cm | `apps/cm/src/entities/auth/api/auth-service.ts` |