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