7.3 KiB
7.3 KiB
서비스별 인증 처리 리팩토링 가이드
1) 목적과 원칙
여러 서비스 앱에 흩어진 인증 로직을 @common/auth 기반으로 통일하기 위한 표준 가이드입니다.
이 문서를 위에서 아래로 적용하면 authService + AuthWrapper 패턴이 갖춰집니다.
핵심 원칙:
- 인증 정책·유틸은
@common/auth에 모은다. - 서비스별 차이는
authConfig설정으로만 표현한다. - 앱은 API 오케스트레이션(
authService)과 진입점(AuthWrapper)만 구현한다. login/silentRefresh성공 시 토큰을 저장한다.- 세션 연장은 confirm 확인 시
silentRefresh로 수행한다.
→ 세션 연장 Confirm 적용 가이드
참고 구현: 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. 런타임 흐름
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는 세션 연장 가이드에서 적용한다.
3) 공통 계약
3-1. AuthConfig
interface AuthConfig {
authUserId: string;
claimName: string;
cookieDomain?: string;
accessToken?: string;
refreshToken?: string;
}
3-2. AuthService
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
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
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
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
setToken(CLAIM_NAME, accessToken);
setToken(AUTH_USER_ID, tokenId);
silentRefresh
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
declineSessionExtend: clearRefreshTimer,
| 사용자 선택 | 호출 |
|---|---|
| confirm 확인 | silentRefresh() |
| confirm 취소 | declineSessionExtend() |
logout
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
export const authService: AuthService = {
login,
silentRefresh,
declineSessionExtend: clearRefreshTimer,
logout,
};
4-5. AuthWrapper
파일: entities/auth/lib/auth-wrapper.tsx
인증 체크와 auth:logout 수신을 구현한다.
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
export { authService } from "./api/auth-service";
export { AuthWrapper } from "./lib/auth-wrapper";
export { tokenManager } from "./lib/token-manager";
4-7. 레이아웃·Provider 연결
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 적용 가이드를 진행한다.
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 |