# @common/auth 인증 관련 공통 기능을 제공하는 독립 패키지입니다. 의존성 주입 패턴을 통해 각 앱의 환경변수를 주입받아 재사용 가능한 토큰 관리 기능을 제공합니다. ## 특징 - **의존성 주입**: 환경변수를 직접 참조하지 않고 설정 객체를 주입받음 - **재사용성**: 모든 앱에서 동일한 로직 사용 가능 - **타입 안정성**: TypeScript 인터페이스로 계약 보장 - **테스트 가능성**: Mock 설정 주입으로 단위 테스트 용이 ## 패키지 구조 ``` packages/common/auth/ ├── package.json # 패키지 정의 ├── tsconfig.json # TypeScript 설정 ├── index.ts # 진입점 (Public API) ├── types.ts # 타입 정의 ├── token-manager.ts # 토큰 관리 로직 ├── cookie.ts # 쿠키 유틸리티 ├── jwt-utils.ts # JWT 유틸리티 └── README.md # 문서 ``` ## 설치 각 앱의 `package.json`에 의존성을 추가합니다: ```json { "dependencies": { "@common/auth": "workspace:*" } } ``` ## 사용 방법 ### 1. 설정 파일 생성 각 앱에서 자신의 환경변수로 `AuthConfig` 객체를 생성합니다. ```typescript // apps/cs/src/shared/config/auth.ts import type { AuthConfig } from "@common/auth/types"; export const authConfig: AuthConfig = { authUserId: process.env.NEXT_PUBLIC_AUTH_USER_ID || "token-id", claimName: process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization", cookieDomain: process.env.NEXT_PUBLIC_COOKIE_DOMAIN, accessToken: process.env.NEXT_PUBLIC_ACCESS_TOKEN || "access-token", refreshToken: process.env.NEXT_PUBLIC_REFRESH_TOKEN || "refresh-token", }; ``` ### 2. TokenManager 인스턴스 생성 설정을 주입하여 토큰 매니저 인스턴스를 생성합니다. ```typescript // apps/cs/src/entities/auth/lib/token-manager.ts import { createTokenManager } from "@common/auth"; import { authConfig } from "@/shared/config/auth"; export const tokenManager = createTokenManager(authConfig); // 편의를 위한 re-export export const { getToken, setToken, removeToken, setupRefreshTimer, clearAuthToken, getAccessToken, getRefreshToken, getUserId, } = tokenManager; ``` ### 3. 컴포넌트에서 사용 ```typescript // apps/cs/src/features/auth/ui/LoginForm.tsx import { tokenManager } from "@/entities/auth/lib/token-manager"; import { authConfig } from "@/shared/config/auth"; export function LoginForm() { const handleLogin = async (credentials: LoginCredentials) => { const response = await loginApi(credentials); // 토큰 저장 tokenManager.setToken(authConfig.claimName, response.accessToken); tokenManager.setToken(authConfig.refreshToken, response.refreshToken); // 또는 편의 메서드 사용 // tokenManager.getAccessToken(); // tokenManager.getRefreshToken(); // tokenManager.getUserId(); }; return ( // ... JSX ); } ``` ## API 문서 ### `createTokenManager(config: AuthConfig): TokenManager` 토큰 매니저 인스턴스를 생성하는 팩토리 함수입니다. **Parameters:** - `config: AuthConfig` - 인증 설정 객체 **Returns:** - `TokenManager` - 토큰 관리 메서드를 포함한 객체 ### TokenManager 메서드 #### `getToken(key: string): string | undefined` 지정된 키의 쿠키 값을 가져옵니다. ```typescript const token = tokenManager.getToken("access-token"); ``` #### `setToken(key: string, value: string): void` 토큰을 쿠키에 저장합니다. 자동으로 도메인, secure, sameSite 설정을 적용합니다. ```typescript tokenManager.setToken("access-token", "eyJhbGc..."); ``` #### `removeToken(key: string): void` 지정된 키의 쿠키를 삭제합니다. ```typescript tokenManager.removeToken("access-token"); ``` #### `setupRefreshTimer(callback: () => Promise, delayMs: number): void` 토큰 갱신 타이머를 설정합니다. ```typescript tokenManager.setupRefreshTimer(async () => { await refreshAccessToken(); }, 3600000); // 1시간 후 ``` #### `clearAuthToken(): void` 모든 인증 토큰을 삭제하고 타이머를 정리합니다. ```typescript tokenManager.clearAuthToken(); ``` #### `getAccessToken(): string | undefined` 액세스 토큰을 가져오는 편의 메서드입니다. ```typescript const accessToken = tokenManager.getAccessToken(); ``` #### `getRefreshToken(): string | undefined` 리프레시 토큰을 가져오는 편의 메서드입니다. ```typescript const refreshToken = tokenManager.getRefreshToken(); ``` #### `getUserId(): string | undefined` 사용자 ID 토큰을 가져오는 편의 메서드입니다. ```typescript const userId = tokenManager.getUserId(); ``` ### JWT 유틸리티 #### `getJwtExpiry(token: string): number | null` JWT 토큰의 만료 시간(Unix timestamp, 초 단위)을 반환합니다. ```typescript import { getJwtExpiry } from "@common/auth"; const expiry = getJwtExpiry(token); if (expiry) { console.log("Token expires at:", new Date(expiry * 1000)); } ``` #### `getRemainingMs(token: string): number` 토큰 만료 1분 전까지 남은 시간(밀리초)을 반환합니다. ```typescript import { getRemainingMs } from "@common/auth"; const remaining = getRemainingMs(token); if (remaining > 0) { console.log("Token valid for:", remaining, "ms"); } ``` ### 쿠키 유틸리티 #### `getCookie(name: string): string | undefined` 쿠키 값을 가져옵니다. ```typescript import { getCookie } from "@common/auth"; const value = getCookie("my-cookie"); ``` #### `setCookie(name: string, value: string, options?: CookieOptions): void` 쿠키를 설정합니다. ```typescript import { setCookie } from "@common/auth"; setCookie("my-cookie", "value", { secure: true, sameSite: "lax", path: "/", }); ``` #### `removeCookie(name: string, options?: CookieOptions): void` 쿠키를 삭제합니다. ```typescript import { removeCookie } from "@common/auth"; removeCookie("my-cookie", { path: "/" }); ``` ## 타입 정의 ### `AuthConfig` ```typescript interface AuthConfig { authUserId: string; // 사용자 ID 토큰 키 claimName: string; // 액세스 토큰 키 cookieDomain?: string; // 쿠키 도메인 (선택) accessToken?: string; // 액세스 토큰 키 (선택) refreshToken?: string; // 리프레시 토큰 키 (선택) } ``` ### `CookieOptions` ```typescript interface CookieOptions { secure?: boolean; sameSite?: "strict" | "lax" | "none"; domain?: string; path?: string; expires?: number | Date; } ``` ## 마이그레이션 가이드 ### 기존 앱에서 @common/auth로 마이그레이션 #### 1. 의존성 추가 ```bash # 앱 디렉토리에서 cd apps/your-app ``` `package.json`에 추가: ```json { "dependencies": { "@common/auth": "workspace:*" } } ``` ```bash pnpm install ``` #### 2. 설정 파일 생성 ```typescript // apps/your-app/src/shared/config/auth.ts import type { AuthConfig } from "@common/auth/types"; export const authConfig: AuthConfig = { authUserId: process.env.NEXT_PUBLIC_AUTH_USER_ID || "token-id", claimName: process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization", cookieDomain: process.env.NEXT_PUBLIC_COOKIE_DOMAIN, accessToken: process.env.NEXT_PUBLIC_ACCESS_TOKEN || "access-token", refreshToken: process.env.NEXT_PUBLIC_REFRESH_TOKEN || "refresh-token", }; ``` #### 3. TokenManager 수정 **Before (기존 코드):** ```typescript // apps/your-app/src/entities/auth/lib/token-manager.ts import { AUTH_USER_ID, CLAIM_NAME } from "@/shared/config"; import { getCookie, setCookie, removeCookie } from "@/shared/lib"; export function getToken(key: string): string | undefined { return getCookie(key); } export function clearAuthToken(): void { removeToken(CLAIM_NAME); removeToken(AUTH_USER_ID); } ``` **After (@common/auth 사용):** ```typescript // apps/your-app/src/entities/auth/lib/token-manager.ts import { createTokenManager } from "@common/auth"; import { authConfig } from "@/shared/config/auth"; export const tokenManager = createTokenManager(authConfig); // 기존 코드와의 호환성을 위한 re-export export const { getToken, setToken, removeToken, setupRefreshTimer, clearAuthToken, getAccessToken, getRefreshToken, getUserId, } = tokenManager; ``` #### 4. 사용처 업데이트 **클래스 기반 (st, cm 앱):** ```typescript // Before import { TokenManager } from "@/entities/auth/lib/token-manager"; const tokenManager = TokenManager.getInstance(); // After import { tokenManager } from "@/entities/auth/lib/token-manager"; // 그대로 사용 가능 ``` **함수형 (cs 앱):** ```typescript // Before import { getToken, setToken, clearAuthToken } from "@/entities/auth/lib/token-manager"; // After import { getToken, setToken, clearAuthToken } from "@/entities/auth/lib/token-manager"; // 그대로 사용 가능 (re-export 덕분) ``` ## 다른 앱에서 사용 예시 ### AS 앱 ```typescript // apps/as/src/shared/config/auth.ts import type { AuthConfig } from "@common/auth/types"; export const authConfig: AuthConfig = { authUserId: process.env.NEXT_PUBLIC_AS_AUTH_USER_ID || "as-token-id", claimName: process.env.NEXT_PUBLIC_AS_CLAIM_NAME || "X-AS-Auth", cookieDomain: process.env.NEXT_PUBLIC_AS_COOKIE_DOMAIN, accessToken: process.env.NEXT_PUBLIC_AS_ACCESS_TOKEN || "as-access-token", refreshToken: process.env.NEXT_PUBLIC_AS_REFRESH_TOKEN || "as-refresh-token", }; ``` ### ST 앱 ```typescript // apps/st/src/shared/config/auth.ts import type { AuthConfig } from "@common/auth/types"; export const authConfig: AuthConfig = { authUserId: process.env.NEXT_PUBLIC_AUTH_USER_ID || "token-id", claimName: process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization", cookieDomain: ".hasuon.or.kr", // 하드코딩된 도메인도 설정으로 이동 accessToken: process.env.NEXT_PUBLIC_ACCESS_TOKEN || "access-token", refreshToken: process.env.NEXT_PUBLIC_REFRESH_TOKEN || "refresh-token", }; ``` ## 환경변수 설정 각 앱의 `.env.local` 파일: ```bash # CS 앱 NEXT_PUBLIC_CLAIM_NAME=Authorization NEXT_PUBLIC_ACCESS_TOKEN=access-token NEXT_PUBLIC_REFRESH_TOKEN=refresh-token NEXT_PUBLIC_AUTH_USER_ID=token-id NEXT_PUBLIC_COOKIE_DOMAIN=.example.com # AS 앱 (다른 설정 사용 가능) NEXT_PUBLIC_AS_CLAIM_NAME=X-AS-Auth NEXT_PUBLIC_AS_ACCESS_TOKEN=as-access-token NEXT_PUBLIC_AS_REFRESH_TOKEN=as-refresh-token NEXT_PUBLIC_AS_AUTH_USER_ID=as-token-id NEXT_PUBLIC_AS_COOKIE_DOMAIN=.as.example.com ``` ## 테스트 예시 ```typescript import { createTokenManager } from "@common/auth"; import type { AuthConfig } from "@common/auth/types"; describe("TokenManager", () => { const mockConfig: AuthConfig = { authUserId: "test-user-id", claimName: "test-claim", cookieDomain: ".test.com", accessToken: "test-access", refreshToken: "test-refresh", }; it("should create token manager with config", () => { const tokenManager = createTokenManager(mockConfig); expect(tokenManager).toBeDefined(); expect(tokenManager.getToken).toBeDefined(); }); it("should use injected config", () => { const tokenManager = createTokenManager(mockConfig); // 테스트 로직... }); }); ``` ## 주의사항 1. **환경변수는 빌드 타임에 주입됩니다**: `process.env.NEXT_PUBLIC_*` 값은 빌드 시점에 정적으로 치환됩니다. 2. **쿠키 도메인 자동 감지**: `cookieDomain`을 설정하지 않으면 자동으로 현재 호스트명을 사용합니다. - `localhost` / `127.0.0.1`: 도메인 설정 안 함 - 기타: 현재 호스트명 사용 3. **HTTPS 자동 감지**: `secure` 옵션은 현재 프로토콜에 따라 자동 설정됩니다. 4. **타이머 관리**: `setupRefreshTimer`는 이전 타이머를 자동으로 정리합니다. ## 문제 해결 ### TypeScript 오류: "Cannot find module '@common/auth'" 1. `pnpm install` 실행 2. TypeScript 서버 재시작 3. `tsconfig.json`의 `paths` 설정 확인 ### 쿠키가 저장되지 않음 1. 브라우저 개발자 도구에서 쿠키 확인 2. `cookieDomain` 설정 확인 3. HTTPS 환경에서 `secure: true` 확인 ### 토큰이 다른 앱에서 공유되지 않음 1. `cookieDomain` 설정이 공통 도메인(예: `.example.com`)인지 확인 2. 각 앱의 토큰 키 이름이 다른지 확인 (충돌 방지) ## 라이선스 ISC