docs: authService 리팩토링 문서 업데이트

This commit is contained in:
2026-08-03 17:24:02 +09:00
parent 5031f0c582
commit d64a694e20
2 changed files with 320 additions and 251 deletions
+188 -88
View File
@@ -1,32 +1,97 @@
# 서비스별 인증 처리 리팩토링 가이드 # 서비스별 인증 처리 리팩토링 가이드
## 1) 문서 목적 ## 1) 목적과 원칙
여러 서비스 앱에서 중복 구현된 인증 로직을 공통 모듈(`@common/auth`) 기반으로 통일하기 위한 표준 가이드 여러 서비스 앱에 흩어진 인증 로직을 `@common/auth` 기반으로 통일하기 위한 표준 가이드입니다.
이 문서를 **위에서 아래로** 적용하면 `authService` + `AuthWrapper` 패턴이 갖춰집니다.
핵심 원칙: 핵심 원칙:
- 인증 정책은 공통 모듈에 모은다. - 인증 **정책·유틸**은 `@common/auth`에 모은다.
- 서비스별 차이는 설정(`authConfig`)으로만 표현한다. - 서비스별 차이는 `authConfig` 설정으로만 표현한다.
- 토큰 갱신 타이머는 JWT `exp` 기준으로 계산한다. - 앱은 API 오케스트레이션(`authService`)과 진입점(`AuthWrapper`)만 구현한다.
- `login` / `silentRefresh` 성공 시 토큰을 저장한다.
- 세션 연장은 confirm **확인** 시 `silentRefresh`로 수행한다.
→ [세션 연장 Confirm 적용 가이드](./session-extend-confirm-guide.md)
## 2) 공통 아키텍처 참고 구현: `apps/as/src/entities/auth/api/auth-service.ts`
인증 관련 책임을 아래처럼 분리한다. ---
- `shared/config/env.ts`: 환경 변수 노출 ## 2) 목표 아키텍처
- `shared/config/auth.ts`: 서비스별 인증 키/도메인 설정
- `entities/auth/lib/token-manager.ts`: 공통 토큰 매니저 생성/re-export
- `entities/auth/api/*`: 로그인/토큰 갱신 API 오케스트레이션
- 레이아웃: BaseLayout 컴포넌트에 `tokenManager` 주입
## 3) 단계별 리팩토링 방법 ### 2-1. 레이어 책임
### 3-1. 환경 변수 정의 통일 (`shared/config/env.ts`) | 레이어 | 경로 | 역할 |
| --- | --- | --- |
| 환경 변수 | `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 ```ts
export const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization"; export const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization";
@@ -38,11 +103,9 @@ export const REFRESH_TOKEN =
export const COOKIE_DOMAIN = process.env.NEXT_PUBLIC_COOKIE_DOMAIN; export const COOKIE_DOMAIN = process.env.NEXT_PUBLIC_COOKIE_DOMAIN;
``` ```
### 3-2. 서비스별 인증 설정 분리 (`shared/config/auth.ts`) ### 4-2. 인증 설정
하드코딩된 키명을 제거하고 `AuthConfig`로 매핑한다. 파일: `shared/config/auth.ts`
예시 코드:
```ts ```ts
import type { AuthConfig } from "@common/auth"; import type { AuthConfig } from "@common/auth";
@@ -63,11 +126,9 @@ export const authConfig: AuthConfig = {
}; };
``` ```
### 3-3. 토큰 매니저 공통화 (`entities/auth/lib/token-manager.ts`) ### 4-3. 토큰 매니저
서비스 고유 `TokenManager` 구현을 제거하고 `createTokenManager`를 사용한다. 파일: `entities/auth/lib/token-manager.ts`
예시 코드:
```ts ```ts
import { authConfig } from "@/shared/config/auth"; import { authConfig } from "@/shared/config/auth";
@@ -79,109 +140,148 @@ export const {
getToken, getToken,
setToken, setToken,
removeToken, removeToken,
setupRefreshTimer,
clearRefreshTimer, clearRefreshTimer,
clearAuthToken,
getAccessToken, getAccessToken,
getRefreshToken, getRefreshToken,
getUserId, getUserId,
} = tokenManager; } = tokenManager;
``` ```
### 3-4. 로그인 성공 처리 표준화 (`entities/auth/api/login-service.ts`) ### 4-4. 인증 서비스
로그인 성공 시 토큰 저장과 자동 갱신 타이머 설정을 한 흐름으로 고정한다. 파일: `entities/auth/api/auth-service.ts`
참고: `apps/as/src/entities/auth/api/auth-service.ts`
예시 코드: #### login
```ts ```ts
import { getRemainingMs } from "@common/auth"; setToken(CLAIM_NAME, accessToken);
import { setToken, setupRefreshTimer } from "../lib/token-manager"; setToken(AUTH_USER_ID, tokenId);
async function onLoginSuccess(params: {
accessToken: string;
tokenId: string;
refresh: () => Promise<void>;
}) {
setToken(authConfig.claimName, params.accessToken);
setToken(authConfig.authUserId, params.tokenId);
const remainingMs = getRemainingMs(params.accessToken);
if (remainingMs <= 0) return;
setupRefreshTimer(params.refresh, remainingMs);
}
``` ```
### 3-5. 토큰 갱신 실패 처리 표준화 #### silentRefresh
토큰 갱신 실패 시 인증 토큰을 일괄 정리한다.
예시 코드:
```ts ```ts
import { clearAuthToken } from "../lib/token-manager";
async function silentRefresh() { async function silentRefresh() {
try { try {
... // SILENT_REFRESH API → setToken(...)
} catch (error) { } catch (error) {
clearAuthToken(); // clearAuthToken은 내부에서 this를 쓰므로 tokenManager 객체로 호출
throw error; tokenManager.clearAuthToken();
if (typeof window !== "undefined") {
window.dispatchEvent(new CustomEvent("auth:logout"));
}
} }
} }
``` ```
### 3-6. 공통 레이아웃/쉘에 토큰 매니저 주입 #### declineSessionExtend
공통 컴포넌트가 인증 기능을 직접 사용할 수 있도록 주입한다. ```ts
declineSessionExtend: clearRefreshTimer,
```
예시 코드: | 사용자 선택 | 호출 |
| --- | --- |
| confirm **확인** | `silentRefresh()` |
| confirm **취소** | `declineSessionExtend()` |
```tsx #### logout
import { tokenManager } from "@/entities/auth";
export function AppLayout({ children }: { children: React.ReactNode }) { ```ts
return ( async function logout() {
<BaseLayout tokenManager.clearAuthToken();
tokenManager={tokenManager} clearDeviceId();
...
> if (typeof window !== "undefined") {
{children} window.dispatchEvent(new CustomEvent("auth:logout"));
</BaseLayout> }
);
} }
``` ```
### 3-7. 배럴 파일 re-export 정리 (`entities/auth/index.ts`) httpOnly refresh 쿠키 삭제가 필요하면 `fetch("/api/auth/logout")`를 먼저 호출한다 (`apps/st` 참고).
외부에서 인증 엔티티를 일관된 경로로 사용하도록 re-export한다. #### export
예시 코드:
```ts ```ts
export { LoginApi } from "./api/login-service"; 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 { AuthWrapper } from "./lib/auth-wrapper";
export { tokenManager } from "./lib/token-manager"; export { tokenManager } from "./lib/token-manager";
``` ```
## 4) 서비스별 적용 체크리스트 ### 4-7. 레이아웃·Provider 연결
각 서비스 담당자는 아래 항목을 모두 충족해야 한다. ```tsx
import { authService } from "@/entities/auth";
- `authConfig`에 서비스별 키명이 정확히 매핑되어 있다. <BaseLayout authService={authService} /* ... */>
- 서비스 내부 커스텀 TokenManager 클래스가 제거되었다. {children}
- 로그인/갱신 로직이 `getRemainingMs` 기반 타이머를 사용한다. </BaseLayout>
- BaseLayout 컴포넌트에 `tokenManager`가 주입되어 있다. ```
- 외부 참조 경로가 `entities/auth` 배럴 경로로 통일되어 있다.
## 5) 세션 연장 Confirm 적용 (후속 단계) `AppProvider`에 `AuthWrapper`를 마운트한다.
리팩토링이 완료된 서비스(`authService` + `AuthWrapper` + `ConfirmProvider`)는 [세션 연장 Confirm 적용 가이드](./session-extend-confirm-guide.md)를 따라 JWT 만료 5분 전 confirm UX를 추가한다. ---
추가 체크 항목: ## 5) 적용 체크리스트
- `token-manager`에서 `clearRefreshTimer`를 re-export한다. - [ ] `env.ts`에 인증 관련 환경 변수가 정의되어 있다
- `AuthWrapper`에 `useSessionExtendConfirm`을 연결한다. - [ ] `authConfig`가 `AuthConfig`로 매핑되어 있다
- `clearRefreshTimer`는 앱 `token-manager` 싱글톤을 통해 전달한다 (`authService`의 `setupRefreshTimer`와 동일 인스턴스). - [ ] `createTokenManager(authConfig)`로 tokenManager를 생성한다
- `ConfirmProvider` 하위에 `AuthWrapper`와 `ConfirmDialog`가 마운트되어 있다. - [ ] `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` |
+132 -163
View File
@@ -1,107 +1,97 @@
# 세션 연장 Confirm 적용 가이드 # 세션 연장 Confirm 적용 가이드
## 1) 문서 목적 ## 1) 목적과 원칙
JWT 만료 **5분 전**에 `"로그인을 연장하시겠습니까?"` confirm 창을 띄우고, 사용자 선택에 따라 세션을 연장하거나 자동 갱신을 중단하는 기능을 서비스 앱에 적용하기 위한 가이드입니다. JWT 만료 **5분 전**에 `"로그인을 연장하시겠습니까?"` confirm을 띄우고, 선택에 따라 세션을 연장하는 UX를 서비스 앱에 적용하기 위한 가이드입니다.
이 문서는 [서비스별 인증 처리 리팩토링 가이드](./auth-service-refactoring-guide.md)의 **후속 단계**로 읽습니다. 이 문서는 [서비스별 인증 처리 리팩토링 가이드](./auth-service-refactoring-guide.md)의 **후속 단계**입니다.
`@common/auth` 기반 `authService` + `token-manager` + `AuthWrapper` 패턴이 먼저 갖춰져 있어야 합니다.
핵심 원칙:
- confirm UX는 `@common/components` **Header**의 `useSessionExtendConfirm`이 담당한다.
- confirm **확인** → `authService.silentRefresh()`
- confirm **취소** → `authService.declineSessionExtend()`
- 앱은 `ConfirmProvider` 계층과 `authService` 주입을 맞춘다.
**적용 완료**: `cs`, `as`, `st`
---
## 2) 선행 조건 ## 2) 선행 조건
아래 항목이 완료된 서비스만 이 가이드를 적용합니다. [리팩토링 가이드 체크리스트](./auth-service-refactoring-guide.md#5-적용-체크리스트)를 통과한 서비스만 진행한다.
| 항목 | 확인 | | 항목 | 리팩토링 가이드 |
| -------------------------------------------------------------------------------- | ------------------------------------------------------------------- | | --- | --- |
| `createTokenManager(authConfig)` 사용 | [3-3. 토큰 매니저 공통화](./auth-service-refactoring-guide.md) | | `authService` (`silentRefresh`, `declineSessionExtend`) | [4-4](./auth-service-refactoring-guide.md#44-인증-서비스) |
| `authService.login` / `silentRefresh`에서 `getRemainingMs` + `setupRefreshTimer` | [3-4. 로그인 성공 처리 표준화](./auth-service-refactoring-guide.md) | | `BaseLayout`에 `authService` 주입 | [4-7](./auth-service-refactoring-guide.md#47-레이아웃provider-연결) |
| `authService.declineSessionExtend` 구현 | confirm 취소 시 refresh 타이머 해제 |
| `AuthWrapper`에서 새로고침 시 refresh 타이머 복구 | cs / as / st 참고 |
| `ConfirmProvider` + `ConfirmDialog` 마운트 | AppProvider |
| `ConfirmProvider` **하위**에 `BaseLayout` → `Header` 렌더링 | `useConfirm` 사용 조건 |
| `BaseLayout`에 `authService` prop 전달 | Header까지 전달됨 |
**적용 완료 앱**: `cs`, `as`, `st` ---
**미적용**: `wr`, `sn`, `fo` 등 legacy `LoginApi` 패턴 앱
## 3) 전체 흐름 ## 3) 목표 아키텍처
로그인 이후 JWT `exp`를 기준으로 **두 개의 독립 타이머**가 동작합니다. ### 3-1. 레이어 책임
| 레이어 | 위치 | 역할 |
| --- | --- | --- |
| JWT 시간 계산 | `@common/auth` `jwt-utils.ts` | `getRemainingMs`, confirm 버퍼 상수 |
| 토큰·정리 | `@common/auth` `token-manager.ts` | `setToken`, `clearRefreshTimer`, `clearAuthToken` |
| API 오케스트레이션 | `entities/auth/api/auth-service.ts` | `silentRefresh`, `declineSessionExtend` |
| confirm UX | `@common/components` Header | 카운트다운 + 5분 전 confirm |
| 앱 진입점 | `entities/auth/lib/auth-wrapper.tsx` | auth 체크, `auth:logout` |
| 레이아웃 | layout / BaseLayout wrapper | `authService` → Header |
| UI 쉘 | `app/providers/app-provider.tsx` | `ConfirmProvider`, `ConfirmDialog` |
### 3-2. 런타임 흐름
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
participant Login as authService.login participant Login as authService.login
participant RefreshTimer as refreshTimer
participant Header as Header participant Header as Header
participant User as 사용자 participant User as 사용자
participant API as authService.silentRefresh participant API as authService.silentRefresh
Login->>RefreshTimer: setupRefreshTimer(exp - 1분) Login->>Login: setToken
Header->>Header: confirm 예약(exp - 5분) Header->>Header: confirm 예약(exp - 5분)
Note over Header: 만료 5분 전 Note over Header: 만료 5분 전
Header->>User: 로그인을 연장하시겠습니까? Header->>User: 로그인을 연장하시겠습니까?
alt 확인 alt 확인
User->>API: silentRefresh() User->>API: silentRefresh()
API->>RefreshTimer: 새 토큰 기준 타이머 재설정 API->>API: setToken
Header->>Header: confirm 타이머 재설정 Header->>Header: confirm 타이머 재설정
else 취소 else 취소
Header->>API: declineSessionExtend() Header->>Header: declineSessionExtend()
API->>RefreshTimer: clearRefreshTimer()
Note over RefreshTimer: 취소시 만료까지 자동 갱신 없음
end end
``` ```
| 시점 | 동작 | 담당 | | 시점 | 동작 | 담당 |
| --------------------------- | ------------------------------------- | ------------------------------------------- | | --- | --- | --- |
| 로그인 / silentRefresh 성공 | 만료 1분 전 자동 `silentRefresh` 예약 | `authService` → `setupRefreshTimer` | | 로그인 / silentRefresh 성공 | 토큰 저장 | `authService` |
| 만료 5분 전 | confirm 표시 | `Header` → `useSessionExtendConfirm` | | 만료 5분 전 | confirm 표시 | Header → `useSessionExtendConfirm` |
| confirm **확인** | 즉시 `silentRefresh` + 타이머 재설정 | Header → `authService.silentRefresh` | | confirm **확인** | `silentRefresh` + confirm 타이머 재설정 | Header → `authService.silentRefresh` |
| confirm **취소** | 자동 `silentRefresh` 타이머 해제 | Header → `authService.declineSessionExtend` | | confirm **취소** | `declineSessionExtend` | Header → `authService.declineSessionExtend` |
| 토큰 완전 만료 | logout | Header |
### 3-1. 시간 계산 (`getRemainingMs`) ---
`[packages/common/auth/jwt-utils.ts](../packages/common/auth/jwt-utils.ts)` ## 4) 공통 계약
```typescript ### 4-1. 시간 버퍼 (`getRemainingMs`)
export const REFRESH_BUFFER_MS = 60_000; // silent refresh: 만료 1분 전
파일: `packages/common/auth/jwt-utils.ts`
```ts
export const SESSION_EXTEND_CONFIRM_BUFFER_MS = 5 * 60_000; // confirm: 만료 5분 전 export const SESSION_EXTEND_CONFIRM_BUFFER_MS = 5 * 60_000; // confirm: 만료 5분 전
getRemainingMs(token); // → refresh 타이머 delay
getRemainingMs(token, SESSION_EXTEND_CONFIRM_BUFFER_MS); // → confirm 타이머 delay getRemainingMs(token, SESSION_EXTEND_CONFIRM_BUFFER_MS); // → confirm 타이머 delay
getRemainingMs(token, 0) > 0; // → 토큰 미만료 여부 getRemainingMs(token, 0) > 0; // → 토큰 미만료 여부
``` ```
`bufferMs` 인자 하나로 용도를 구분합니다. 기존 `getRemainingMs(token)` 호출부는 변경하지 않아도 됩니다. ### 4-2. Header ↔ AuthService
## 4) 공통 모듈 변경 사항 공통 Header가 아래처럼 연결한다.
### 4-1. `@common/auth` — `AuthService` / `clearRefreshTimer` ```ts
`[packages/common/auth/types.ts](../packages/common/auth/types.ts)`
```typescript
export interface AuthService {
login(payload: AuthPayload): Promise<void>;
silentRefresh(): Promise<void>;
declineSessionExtend(): void; // confirm 취소 시 refresh 타이머 해제
logout(): Promise<void>;
}
```
`[packages/common/auth/token-manager.ts](../packages/common/auth/token-manager.ts)`
- `setupRefreshTimer`: 만료 1분 전 `silentRefresh` 예약 (기존)
- `clearRefreshTimer`: 예약된 refresh 타이머 해제 (신규)
- `clearAuthToken`: 내부에서 `clearRefreshTimer` 호출
`refreshTimer`는 `createTokenManager()` 클로저 안의 **앱당 싱글톤** 변수입니다.
`authService.declineSessionExtend()`가 내부에서 `clearRefreshTimer()`를 호출하므로, Header는 token-manager를 직접 import하지 않습니다.
### 4-2. `@common/components` — Header + `useSessionExtendConfirm`
`[packages/common/components/src/ui/header/index.tsx](../packages/common/components/src/ui/header/index.tsx)`에서 훅을 실행합니다.
```typescript
useSessionExtendConfirm({ useSessionExtendConfirm({
enabled: enabled:
mounted && mounted &&
@@ -110,68 +100,27 @@ useSessionExtendConfirm({
!!authService && !!authService &&
!isAtLoginRedirectUrl(getAuthLoginRedirectUrl()), !isAtLoginRedirectUrl(getAuthLoginRedirectUrl()),
getAccessToken: () => getCookie(appConfig.CLAIM_NAME ?? ""), getAccessToken: () => getCookie(appConfig.CLAIM_NAME ?? ""),
silentRefresh: authService.silentRefresh, silentRefresh: authService?.silentRefresh ?? (async () => {}),
clearRefreshTimer: authService.declineSessionExtend, declineSessionExtend: authService?.declineSessionExtend ?? (() => {}),
}); });
``` ```
| enabled 조건 | 이유 | ### 4-3. Provider 계층
| ---------------------------- | ------------------------------- |
| `mounted` | hydration 후 타이머 시작 |
| `isLoggedIn` | 비로그인 제외 |
| `!viewOnly` | `VBaseLayout` 등 조회 전용 제외 |
| `!!authService` | legacy 앱 자동 비활성 |
| `!isAtLoginRedirectUrl(...)` | 로그인 페이지 제외 |
세션 만료 **카운트다운 UI**와 confirm 로직이 Header에 응집됩니다. ```
ConfirmProvider
## 5) 서비스별 적용 단계 └─ AuthWrapper
└─ … → BaseLayout → Header
### 5-1. `auth-service`에 `declineSessionExtend` 추가 └─ ConfirmDialog
`[apps/cs/src/entities/auth/api/auth-service.ts](../apps/cs/src/entities/auth/api/auth-service.ts)` 참고:
```typescript
import { clearRefreshTimer } from "../lib/token-manager";
function declineSessionExtend(): void {
clearRefreshTimer();
}
export const authService: AuthService = {
login,
silentRefresh,
declineSessionExtend,
logout,
};
``` ```
### 5-2. `BaseLayout`에 `authService` 전달 ---
`[apps/cs/app/(main)/(with-base-layout)/layout.tsx](../apps/cs/app/(main)`/(with-base-layout)/layout.tsx) 참고: ## 5) 적용 순서
```tsx ### 5-1. AppProvider — ConfirmProvider 마운트
<BaseLayout
queryClient={queryClient}
authService={authService}
currentPath={pathname ?? ""}
handleMenuClick={handleMenuClick}
>
```
`BaseLayout`은 `authService`를 `Header`에 그대로 전달합니다. **AuthWrapper에서 훅을 연결하지 않습니다.** 파일: `app/providers/app-provider.tsx`
### 5-3. `AuthWrapper` — refresh 타이머 복구만 유지
`[apps/cs/src/entities/auth/lib/auth-wrapper.tsx](../apps/cs/src/entities/auth/lib/auth-wrapper.tsx)`:
- `useSessionExtendConfirm` **사용하지 않음**
- 페이지 새로고침 시 `setupRefreshTimer(authService.silentRefresh, remainingMs)` 복구 유지
- 인증 체크, `auth:logout` 수신 유지
### 5-4. `AppProvider` 구조 확인
`[apps/cs/src/app/providers/app-provider.tsx](../apps/cs/src/app/providers/app-provider.tsx)` 참고:
```tsx ```tsx
<ConfirmProvider> <ConfirmProvider>
@@ -182,64 +131,84 @@ export const authService: AuthService = {
</ConfirmProvider> </ConfirmProvider>
``` ```
`Header`는 `ConfirmProvider` 하위에서 렌더링되므로 `useConfirm()`이 동작합니다. 참고: `apps/cs/src/app/providers/app-provider.tsx`
## 6) 책임 분리 요약 ### 5-2. BaseLayout — authService 전달
| 레이어 | 파일 | 역할 | ```tsx
| ------------------ | ------------------------------------ | -------------------------------------------------- | import { authService } from "@/entities/auth";
| JWT 시간 계산 | `@common/auth` `jwt-utils.ts` | `getRemainingMs`, 버퍼 상수 |
| refresh 타이머 | `@common/auth` `token-manager.ts` | `setupRefreshTimer`, `clearRefreshTimer` |
| API 오케스트레이션 | `entities/auth/api/auth-service.ts` | login, silentRefresh, declineSessionExtend, logout |
| confirm UX | `@common/components` `Header` | 카운트다운 UI + 5분 전 confirm |
| 앱 진입점 | `entities/auth/lib/auth-wrapper.tsx` | 타이머 복구, auth 체크 |
| 레이아웃 | `app/.../layout.tsx` | `authService` → `BaseLayout` → `Header` |
| UI 쉘 | `app/providers/app-provider.tsx` | ConfirmProvider, ConfirmDialog |
## 7) 엣지 케이스 <BaseLayout authService={authService} /* ... */>
{children}
</BaseLayout>
```
| 상황 | 동작 | ### 5-3. authService — declineSessionExtend
| -------------------- | --------------------------------------------------------------- |
| 토큰 유효 시간 < 5분 | `confirmMs === 0`이면 페이지 진입 즉시 confirm |
| confirm **취소** | `declineSessionExtend()` + 동일 JWT에 재표시 안 함 |
| confirm **확인** | `silentRefresh()` → 새 토큰 기준 confirm/refresh 타이머 재설정 |
| `silentRefresh` 실패 | `authService`가 `auth:logout` 발행 → AuthWrapper 리다이렉트 |
| 로그아웃 | `clearAuthToken`이 refresh 타이머 정리, 훅 cleanup |
| `viewOnly` Header | confirm 비활성 |
| `authService` 미전달 | confirm 비활성 (wr/fo 등) |
| 페이지 새로고침 | AuthWrapper가 refresh 타이머 복구, Header가 confirm 타이머 복구 |
## 8) 적용 체크리스트 리팩토링 [4-4](./auth-service-refactoring-guide.md#44-인증-서비스)에서 이미 구현했다면 확인만 한다.
[리팩토링 가이드 체크리스트](./auth-service-refactoring-guide.md#4-서비스별-적용-체크리스트)에 더해 아래를 확인합니다. ```ts
export const authService: AuthService = {
login,
silentRefresh,
declineSessionExtend: clearRefreshTimer,
logout,
};
```
- [ ] `auth-service`에 `declineSessionExtend` 구현 ---
- [ ] `token-manager`에서 `clearRefreshTimer` re-export
- [ ] `BaseLayout`에 `authService` prop 전달 (→ Header까지)
- [ ] `ConfirmProvider` > `AuthWrapper` > `BaseLayout` > `Header` 계층 확인
- [ ] `AuthWrapper`에 `useSessionExtendConfirm` **없음** 확인
- [ ] 만료 5분 전 confirm 노출 확인
- [ ] 확인 클릭 시 `SILENT_REFRESH` API 호출 확인
- [ ] 취소 클릭 시 만료 1분 전 자동 refresh 미발생 확인
## 9) 검증 방법 ## 6) 엣지 케이스
| 상황 | 동작 |
| --- | --- |
| 토큰 유효 시간 < 5분 | 진입 즉시 confirm |
| confirm **취소** | `declineSessionExtend()` + 동일 JWT에 재표시 안 함 |
| confirm **확인** | `silentRefresh()` → 새 토큰 기준 confirm 타이머 재설정 |
| `silentRefresh` 실패 | `auth:logout` → AuthWrapper 리다이렉트 |
| 로그아웃 | `clearAuthToken`, 훅 cleanup |
| 페이지 새로고침 | Header가 confirm 타이머 재예약 |
---
## 7) 적용 체크리스트
- [ ] `ConfirmProvider` > `AuthWrapper` > `BaseLayout` > `Header` 계층
- [ ] `ConfirmDialog`가 `ConfirmProvider` 하위에 마운트됨
- [ ] `BaseLayout`에 `authService` 전달
- [ ] `authService.declineSessionExtend` 구현
- [ ] 만료 5분 전 confirm 노출
- [ ] 확인 클릭 시 `SILENT_REFRESH` 호출
- [ ] 취소 후 세션이 연장되지 않음
---
## 8) 검증 방법
1. 로그인 후 JWT `exp`가 5분 이내가 되도록 대기하거나 테스트 토큰 사용 1. 로그인 후 JWT `exp`가 5분 이내가 되도록 대기하거나 테스트 토큰 사용
2. confirm 문구 `"로그인을 연장하시겠습니까?"` 표시 확인 2. confirm 문구 `"로그인을 연장하시겠습니까?"` 표시 확인
3. **확인** → Network 탭에서 `SILENT_REFRESH` PUT 호출, 쿠키 갱신, confirm 재예약 3. **확인** → Network에서 `SILENT_REFRESH` PUT, 쿠키 갱신, confirm 재예약
4. **취소** → 만료 1분 전까지 `SILENT_REFRESH` 자동 호출 없음 4. **취소** → 만료 시 로그아웃/리다이렉트
5. 취소 후 세션 만료 → 401 / 로그인 리다이렉트 5. 새로고침 후 confirm 타이머 복구
6. 새로고침 후 refresh·confirm 타이머 복구
## 10) 참고 구현 ---
| 앱 | Header (공통) | auth-service | BaseLayout layout | ## 9) 참고 구현
| --- | ------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| cs | `[packages/common/components/src/ui/header/index.tsx](../packages/common/components/src/ui/header/index.tsx)` | `[apps/cs/src/entities/auth/api/auth-service.ts](../apps/cs/src/entities/auth/api/auth-service.ts)` | `[apps/cs/app/(main)/(with-base-layout)/layout.tsx](../apps/cs/app/(main)`/(with-base-layout)/layout.tsx) |
| as | (동일) | `[apps/as/src/entities/auth/api/auth-service.ts](../apps/as/src/entities/auth/api/auth-service.ts)` | `[apps/as/src/app/layouts/base-layout.tsx](../apps/as/src/app/layouts/base-layout.tsx)` |
| st | (동일) | `[apps/st/src/entities/auth/api/auth-service.ts](../apps/st/src/entities/auth/api/auth-service.ts)` | `[apps/st/app/(main)/(with-base-layout)/layout.tsx](../apps/st/app/(main)`/(with-base-layout)/layout.tsx) |
## 11) 관련 문서 | 앱 | auth-service | BaseLayout / layout | AppProvider |
| --- | --- | --- | --- |
| as (기준) | `apps/as/src/entities/auth/api/auth-service.ts` | `apps/as/src/app/layouts/base-layout.tsx` | (동등 Provider) |
| cs | `apps/cs/src/entities/auth/api/auth-service.ts` | `apps/cs/app/(main)/(with-base-layout)/layout.tsx` | `apps/cs/src/app/providers/app-provider.tsx` |
| st | `apps/st/src/entities/auth/api/auth-service.ts` | `apps/st/app/(main)/(with-base-layout)/layout.tsx` | (동등 Provider) |
- [서비스별 인증 처리 리팩토링 가이드](./auth-service-refactoring-guide.md) — 선행 리팩토링 공통:
- `[packages/common/auth/README.md](../packages/common/auth/README.md)` — `getRemainingMs`, token-manager API
- `packages/common/components/src/ui/header/index.tsx`
- `packages/common/components/src/lib/use-session-extend-confirm.ts`
---
## 10) 관련 문서
- [서비스별 인증 처리 리팩토링 가이드](./auth-service-refactoring-guide.md)
- `packages/common/auth/README.md`