Package Exports
- @thinkingcat/auth-utils
Readme
@thinkingcat/auth-utils
ThinkingCat SSO 서비스를 위한 인증 유틸리티 패키지입니다. JWT 토큰 검증, NextAuth 세션 생성, 쿠키 설정, 역할 기반 접근 제어, 미들웨어 헬퍼 등의 광범위한 인증 기능을 제공합니다.
📑 목차 (Table of Contents)
- 📦 설치 (Installation)
- 📋 요구사항 (Requirements)
- 🐛 디버깅 (Debugging)
- ⚙️ Next.js 설정 (Next.js Configuration)
- 🚀 빠른 시작 (Quick Start)
- 📚 주요 기능 (Features)
- 🔧 API 레퍼런스
- 💡 사용 시나리오
- 🔒 보안 고려사항
- 📝 타입 정의
- 🐛 문제 해결 (Troubleshooting)
- 📦 패키지 정보
📦 설치 (Installation)
npm 사용
npm install @thinkingcat/auth-utilsyarn 사용
yarn add @thinkingcat/auth-utilspnpm 사용
pnpm add @thinkingcat/auth-utils📋 요구사항 (Requirements)
- Next.js: >= 13.0.0
- Node.js: >= 18.0.0
- TypeScript: 권장 (타입 지원)
🐛 디버깅 (Debugging)
이 패키지는 조건부 로깅 시스템을 사용합니다. 기본적으로 프로덕션 환경에서는 로그가 출력되지 않으며, 개발 환경에서만 로그가 출력됩니다.
디버그 로그 활성화
환경 변수 AUTH_UTILS_DEBUG=true를 설정하여 모든 환경에서 디버그 로그를 활성화할 수 있습니다:
# .env.local 또는 환경 변수 설정
AUTH_UTILS_DEBUG=true로그 출력 조건
NODE_ENV === 'development': 자동으로 로그 출력AUTH_UTILS_DEBUG === 'true': 모든 환경에서 로그 출력- 그 외: 로그 출력 안 함 (성능 최적화)
디버그 로그 예시
// 개발 환경 또는 AUTH_UTILS_DEBUG=true일 때만 출력됨
[handleMiddleware] Processing: /admin
[verifyAndRefreshToken] Checking refresh: { hasRefreshToken: true, forceRefresh: false }
[createAuthResponse] JWT created: { hasId: true, hasEmail: true, hasRole: true }이 기능으로 프로덕션 환경에서 불필요한 로그 출력을 방지하여 성능을 최적화합니다.
⚙️ Next.js 설정 (Next.js Configuration)
npm에 배포된 패키지를 사용하는 경우 (권장)
npm에 배포된 패키지를 사용할 때는 별도의 설정이 필요하지 않습니다. 바로 사용할 수 있습니다.
npm install @thinkingcat/auth-utils{
"dependencies": {
"@thinkingcat/auth-utils": "^1.0.17"
}
}설정 불필요: npm에 배포된 패키지는 이미 빌드되어 있으므로 next.config.ts에 추가 설정이 필요 없습니다.
로컬 패키지를 사용하는 경우 (개발 중)
로컬 패키지(file:../packages/auth-token-utils)를 사용하는 경우, Next.js가 모듈을 찾지 못하는 오류가 발생할 수 있습니다.
해결 방법 1: transpilePackages 추가 (권장)
next.config.ts에 다음 설정을 추가하세요:
/** @type {import('next').NextConfig} */
const nextConfig = {
transpilePackages: ["@thinkingcat/auth-utils"],
// ... 기타 설정
};
module.exports = nextConfig;해결 방법 2: npm에 배포된 버전 사용 (가장 안정적)
로컬 패키지 대신 npm에 배포된 버전을 사용하면 설정이 필요 없습니다.
🚀 빠른 시작 (Quick Start)
1. 기본 import
import {
// 토큰 검증 및 생성
verifyToken,
extractRoleFromPayload,
createNextAuthJWT,
encodeNextAuthToken,
// 쿠키 관리
setCustomTokens,
setNextAuthToken,
clearAuthCookies,
// 리다이렉트 및 응답
createRedirectHTML,
createAuthResponse,
redirectToError,
redirectToSSOLogin,
redirectToRoleDashboard,
// 역할 및 접근 제어
getEffectiveRole,
hasRole,
hasAnyRole,
checkRoleAccess,
requiresSubscription,
// 경로 체크
isPublicPath,
isApiPath,
isProtectedApiPath,
// 토큰 유효성 검사
isTokenExpired,
isValidToken,
// SSO 통합
refreshSSOToken,
getRefreshTokenFromSSO,
verifyTokenFromSSO,
validateServiceSubscription,
// 미들웨어
createMiddlewareConfig,
handleMiddleware,
verifyAndRefreshTokenWithNextAuth,
// NextAuth 설정 및 콜백
createNextAuthBaseConfig,
createNextAuthCookies,
handleJWTCallback,
createInitialJWTToken,
createEmptySession,
mapTokenToSession,
getJWTFromCustomTokenCookie,
// 라이센스
checkLicenseKey,
// 타입
ServiceInfo,
ResponseLike,
JWTPayload,
MiddlewareConfig,
MiddlewareOptions,
} from "@thinkingcat/auth-utils";2. 라이센스 키 사용
라이센스 키는 필수입니다. 모든 함수 호출 시 licenseKey 파라미터를 전달해야 합니다.
// 예시: handleMiddleware 사용 시
const response = await handleMiddleware(req, middlewareConfig, {
secret: process.env.NEXTAUTH_SECRET!,
isProduction: process.env.NODE_ENV === "production",
ssoBaseURL: process.env.SSO_BASE_URL!,
licenseKey: process.env.LICENSE_KEY!, // 필수
});중요:
- 라이센스 키는 모든 함수 호출 시 필수 파라미터입니다.
- 라이센스 키가 없거나 유효하지 않으면 함수가 에러를 발생시킵니다.
- 라이센스 키는 SHA-256 해시로 변환되어 모듈 내부의 유효한 키 목록과 비교됩니다.
- 다른 환경 변수(NEXTAUTH_SECRET, SSO_BASE_URL 등)는 이 패키지를 사용하는 애플리케이션에서 관리합니다.
📚 주요 기능 (Features)
이 패키지는 다음 기능을 제공합니다:
- 토큰 검증: JWT access token 검증 및 디코딩
- 역할 추출: payload에서 서비스별 역할 추출
- NextAuth JWT 생성: NextAuth 호환 JWT 객체 생성
- 세션 토큰 인코딩: NextAuth 세션 토큰 생성
- 쿠키 설정: 자체 토큰 및 NextAuth 토큰 쿠키 설정
- 리다이렉트 HTML: 클라이언트 리다이렉트용 HTML 생성
- 완전한 인증 응답: 모든 인증 단계를 한 번에 처리
- 미들웨어 헬퍼: 에러 리다이렉트, 쿠키 삭제, SSO 로그인 리다이렉트 등
- 역할 기반 접근 제어: 경로별 역할 검증 및 접근 제어
- 구독 검증: 서비스 구독 상태 확인
- 토큰 유효성 검사: 토큰 만료 및 유효성 확인
- 경로 체크: 공개 경로, API 경로, 보호된 경로 확인
- 통합 인증 체크: NextAuth와 자체 토큰을 모두 확인하는 통합 함수
- 미들웨어 통합: 완전한 미들웨어 핸들러 함수 제공
- 설정 관리: 미들웨어 설정 생성 및 관리 함수
🔧 API 레퍼런스
토큰 검증 및 생성
verifyToken(accessToken: string, secret: string, licenseKey: string)
JWT access token을 검증하고 디코딩합니다.
파라미터:
accessToken: 검증할 JWT 토큰secret: JWT 서명에 사용할 secret keylicenseKey: 라이센스 키 (필수)
반환값:
- 성공:
{ payload: JWTPayload } - 실패:
null
사용 예시:
const secret = process.env.NEXTAUTH_SECRET!;
const licenseKey = process.env.LICENSE_KEY!;
const result = await verifyToken(accessToken, secret, licenseKey);
if (result) {
const { payload } = result;
console.log("User email:", payload.email);
console.log("User ID:", payload.sub || payload.id);
} else {
console.log("Invalid token");
}extractRoleFromPayload(payload: JWTPayload, serviceId: string, defaultRole?: string)
payload에서 특정 서비스의 역할을 추출합니다.
파라미터:
payload: JWT payload 객체 (JWTPayload 타입)serviceId: 서비스 ID (필수)defaultRole: 기본 역할 (기본값: 'ADMIN')
반환값:
- 추출된 역할 문자열
사용 예시:
const role = extractRoleFromPayload(payload, "myservice", "ADMIN");
// 'ADMIN', 'TEACHER', 'STUDENT' 등createNextAuthJWT(payload: JWTPayload, serviceId: string)
NextAuth와 호환되는 JWT 객체를 생성합니다.
파라미터:
payload: 원본 JWT payload (JWTPayload 타입)serviceId: 서비스 ID (필수)
반환값:
- NextAuth JWT 객체
사용 예시:
const jwt = createNextAuthJWT(payload, "myservice");encodeNextAuthToken(jwt: JWT, secret: string, maxAge?: number)
NextAuth JWT 객체를 인코딩된 세션 토큰으로 변환합니다.
파라미터:
jwt: NextAuth JWT 객체secret: JWT 서명에 사용할 secret keymaxAge: 토큰 유효 기간 (초, 기본값: 30일)
반환값:
- 인코딩된 세션 토큰 문자열
사용 예시:
const secret = process.env.NEXTAUTH_SECRET!;
const jwt = createNextAuthJWT(payload, "myservice");
const sessionToken = await encodeNextAuthToken(jwt, secret);쿠키 관리
setCustomTokens(response: ResponseLike, accessToken: string, optionsOrRefreshToken?, options?)
자체 토큰(access token, refresh token)만 쿠키에 설정합니다.
파라미터:
response: NextResponse 객체 (또는 ResponseLike 인터페이스를 구현한 객체)accessToken: access token 문자열optionsOrRefreshToken:- 문자열인 경우: refresh token
- 객체인 경우: 옵션 객체
{ refreshToken?, cookiePrefix?, isProduction? }
options: 옵션 객체 (refreshToken이 문자열로 전달된 경우)
옵션:
cookiePrefix: 쿠키 이름 접두사 (필수)isProduction: 프로덕션 환경 여부 (기본값: false)refreshToken: refresh token (옵션 객체 내부에 포함 가능)
사용 예시:
// 방법 1: refreshToken이 있는 경우
setCustomTokens(response, accessToken, refreshToken, {
cookiePrefix: "myservice",
isProduction: process.env.NODE_ENV === "production",
});
// 방법 2: refreshToken이 없는 경우
setCustomTokens(response, accessToken, {
cookiePrefix: "myservice",
isProduction: process.env.NODE_ENV === "production",
});설정되는 쿠키:
{cookiePrefix}_access_token: 15분 유효{cookiePrefix}_refresh_token: 30일 유효 (있는 경우)
setNextAuthToken(response: ResponseLike, sessionToken: string, options?)
NextAuth 세션 토큰만 쿠키에 설정합니다.
파라미터:
response: NextResponse 객체sessionToken: NextAuth 세션 토큰options: 옵션 객체
옵션:
isProduction: 프로덕션 환경 여부 (기본값: false)cookieDomain: 쿠키 도메인 (선택사항)
사용 예시:
setNextAuthToken(response, sessionToken, {
isProduction: process.env.NODE_ENV === "production",
cookieDomain: process.env.COOKIE_DOMAIN,
});설정되는 쿠키:
- 개발:
next-auth.session-token - 프로덕션:
__Secure-next-auth.session-token - 유효 기간: 30일
clearAuthCookies(response: NextResponse, cookiePrefix: string)
인증 쿠키를 삭제합니다.
파라미터:
response: NextResponse 객체cookiePrefix: 쿠키 이름 접두사 (필수)
사용 예시:
const response = NextResponse.redirect("/login");
clearAuthCookies(response, "myservice");
return response;역할 및 접근 제어
getEffectiveRole(payload: JWTPayload, serviceId: string, defaultRole?: string)
payload에서 유효한 역할을 가져옵니다.
파라미터:
payload: JWT payload 객체serviceId: 서비스 ID (필수)defaultRole: 기본 역할 (기본값: 'ADMIN')
반환값:
- 역할 문자열
hasRole(payload: JWTPayload, serviceId: string, role: string)
payload에 특정 역할이 있는지 확인합니다.
파라미터:
payload: JWT payload 객체serviceId: 서비스 ID (필수)role: 확인할 역할
반환값:
boolean
hasAnyRole(payload: JWTPayload, serviceId: string, roles: string[])
payload에 여러 역할 중 하나라도 있는지 확인합니다.
파라미터:
payload: JWT payload 객체serviceId: 서비스 ID (필수)roles: 확인할 역할 배열
반환값:
boolean
checkRoleAccess(pathname: string, role: string, config: RoleAccessConfig[])
경로에 대한 역할 접근 권한을 확인합니다.
파라미터:
pathname: 경로role: 사용자 역할config: 역할 접근 설정 배열
반환값:
{ allowed: boolean; message?: string }
requiresSubscription(pathname: string, role: string, subscriptionRequiredPaths: string[], systemAdminRole?: string)
경로가 구독이 필요한지 확인합니다.
파라미터:
pathname: 경로role: 사용자 역할subscriptionRequiredPaths: 구독이 필요한 경로 배열systemAdminRole: 시스템 관리자 역할 (선택사항)
반환값:
boolean
SSO 통합
refreshSSOToken(refreshToken: string, options: { ssoBaseURL: string; authServiceKey?: string })
SSO 서버에서 refresh token을 사용하여 새로운 access token을 발급받습니다.
파라미터:
refreshToken: refresh tokenoptions.ssoBaseURL: SSO 서버 기본 URL (필수)options.authServiceKey: 인증 서비스 키 (선택사항)
반환값:
SSORefreshTokenResponse
사용 예시:
const result = await refreshSSOToken(refreshToken, {
ssoBaseURL: process.env.SSO_BASE_URL!,
authServiceKey: process.env.AUTH_SERVICE_SECRET_KEY,
});getRefreshTokenFromSSO(userId: string, accessToken: string, options: { ssoBaseURL: string; authServiceKey?: string })
SSO 서버에서 사용자의 refresh token을 가져옵니다.
파라미터:
userId: 사용자 IDaccessToken: access tokenoptions.ssoBaseURL: SSO 서버 기본 URL (필수)options.authServiceKey: 인증 서비스 키 (선택사항)
반환값:
string | null
verifyTokenFromSSO(accessToken: string, options: { ssoBaseURL: string; authServiceKey?: string })
SSO 서버에서 토큰을 검증합니다.
파라미터:
accessToken: access tokenoptions.ssoBaseURL: SSO 서버 기본 URL (필수)options.authServiceKey: 인증 서비스 키 (선택사항)
반환값:
Promise<{ isValid: boolean; payload?: JWTPayload }>
validateServiceSubscription(services: ServiceInfo[], serviceId: string, ssoBaseURL: string)
서비스 구독 상태를 확인합니다.
파라미터:
services: 서비스 정보 배열serviceId: 서비스 IDssoBaseURL: SSO 서버 기본 URL (필수)
반환값:
{ isValid: boolean; redirectUrl?: string }
NextAuth 설정 및 콜백
createNextAuthBaseConfig(options)
NextAuth 기본 설정을 생성합니다.
파라미터:
options.secret: NextAuth secret (필수)options.isProduction: 프로덕션 환경 여부 (기본값: false)options.cookieDomain: 쿠키 도메인 (선택)options.signInPath: 로그인 페이지 경로 (기본값: '/login')options.errorPath: 에러 페이지 경로 (기본값: '/login')options.nextAuthUrl: NextAuth URL (선택)options.sessionMaxAge: 세션 최대 유지 시간 (초, 기본값: 30일)options.jwtMaxAge: JWT 최대 유지 시간 (초, 기본값: 30일)
반환값:
- NextAuth 기본 설정 객체
사용 예시:
import { createNextAuthBaseConfig } from "@thinkingcat/auth-utils";
const baseConfig = createNextAuthBaseConfig({
secret: process.env.NEXTAUTH_SECRET!,
isProduction: process.env.NODE_ENV === "production",
cookieDomain: process.env.COOKIE_DOMAIN,
signInPath: "/login",
errorPath: "/login",
nextAuthUrl: process.env.NEXTAUTH_URL,
});
export const authOptions: NextAuthOptions = {
...baseConfig,
// ... 기타 설정
};createNextAuthCookies(options)
NextAuth 쿠키 설정을 생성합니다.
파라미터:
options.isProduction: 프로덕션 환경 여부 (기본값: false)options.cookieDomain: 쿠키 도메인 (선택)
반환값:
- NextAuth 쿠키 설정 객체
handleJWTCallback(token, user?, account?, options?)
JWT 콜백을 위한 통합 헬퍼 함수입니다. 초기 로그인, 토큰 갱신, 커스텀 토큰 읽기를 모두 처리합니다.
파라미터:
token: 기존 JWT 토큰user: 사용자 정보 (초기 로그인 시)account: 계정 정보 (초기 로그인 시)options.secret: NextAuth secret (커스텀 토큰 읽기용)options.licenseKey: 라이센스 키 (커스텀 토큰 읽기용)options.serviceId: 서비스 ID (커스텀 토큰 읽기용)options.cookieName: 커스텀 토큰 쿠키 이름 (기본값: '{serviceId}_access_token')options.debug: 디버깅 로그 출력 여부 (기본값: false)
반환값:
- 업데이트된 JWT 토큰
사용 예시:
import { handleJWTCallback } from "@thinkingcat/auth-utils";
async jwt({ token, user, account }) {
return handleJWTCallback(
token,
user ? {
id: user.id,
email: user.email,
role: user.role,
// ... 기타 사용자 정보
} : null,
account,
{
secret: process.env.NEXTAUTH_SECRET!,
licenseKey: process.env.LICENSE_KEY!,
serviceId: 'myservice',
cookieName: 'myservice_access_token',
debug: true,
}
);
}createInitialJWTToken(token, user, account?)
JWT 콜백에서 초기 로그인 시 토큰 생성 헬퍼입니다.
파라미터:
token: 기존 토큰user: 사용자 정보account: 계정 정보 (선택)
반환값:
- 업데이트된 JWT 토큰
createEmptySession(session)
Session 콜백에서 빈 세션 반환 헬퍼입니다.
파라미터:
session: 기존 세션
반환값:
- 빈 세션 객체
mapTokenToSession(session, token)
Session 콜백에서 토큰 정보를 세션에 매핑하는 헬퍼입니다.
파라미터:
session: 기존 세션token: JWT 토큰
반환값:
- 업데이트된 세션
getJWTFromCustomTokenCookie(cookieName, secret, serviceId, licenseKey)
쿠키에서 커스텀 토큰을 읽어서 NextAuth JWT로 변환하는 헬퍼 함수입니다.
파라미터:
cookieName: 쿠키 이름 (예: 'checkon_access_token')secret: JWT 서명에 사용할 secret keyserviceId: 서비스 ID (필수)licenseKey: 라이센스 키 (필수)
반환값:
- NextAuth JWT 객체 또는 null
미들웨어
createMiddlewareConfig(config: Partial<MiddlewareConfig> & { serviceId: string }, defaults?)
미들웨어 설정을 생성합니다.
파라미터:
config: 미들웨어 설정 객체 (serviceId 필수)defaults: 기본 설정값 (선택사항)
반환값:
- 완전한 미들웨어 설정 객체
사용 예시:
import { createMiddlewareConfig } from "@thinkingcat/auth-utils";
const middlewareConfig = createMiddlewareConfig({
serviceId: "myservice",
publicPaths: ["/login", "/register"],
roleAccessConfig: [
{
paths: ["/admin"],
role: "ADMIN",
message: "관리자만 접근할 수 있습니다.",
},
],
});handleMiddleware(req: NextRequest, config: MiddlewareConfig, options: MiddlewareOptions)
통합 미들웨어 핸들러 함수입니다. 모든 인증, 권한, 구독 체크를 포함합니다.
파라미터:
req: NextRequest 객체config: 미들웨어 설정 (createMiddlewareConfig로 생성)options: 미들웨어 실행 옵션
옵션:
secret: NextAuth Secret (필수)isProduction: 프로덕션 환경 여부 (필수)cookieDomain: 쿠키 도메인 (선택사항)getNextAuthToken: NextAuth 토큰을 가져오는 함수 (선택사항)ssoBaseURL: SSO 서버 기본 URL (필수)authServiceKey: 인증 서비스 키 (선택사항)
반환값:
- NextResponse 또는 null (다음 미들웨어로 진행)
사용 예시:
import { withAuth } from "next-auth/middleware";
import { NextRequest, NextResponse } from "next/server";
import { getToken } from "next-auth/jwt";
import {
createMiddlewareConfig,
handleMiddleware,
} from "@thinkingcat/auth-utils";
const middlewareConfig = createMiddlewareConfig({
serviceId: "myservice",
publicPaths: ["/login", "/register"],
roleAccessConfig: [
{
paths: ["/admin"],
role: "ADMIN",
message: "관리자만 접근할 수 있습니다.",
},
],
});
export default withAuth(
async function middleware(req: NextRequest) {
const response = await handleMiddleware(req, middlewareConfig, {
secret: process.env.NEXTAUTH_SECRET!,
isProduction: process.env.NODE_ENV === "production",
cookieDomain: process.env.COOKIE_DOMAIN,
ssoBaseURL: process.env.SSO_BASE_URL!,
getNextAuthToken: async (req: NextRequest) => {
return await getToken({ req, secret: process.env.NEXTAUTH_SECRET! });
},
authServiceKey: process.env.AUTH_SERVICE_SECRET_KEY,
});
return response || NextResponse.next();
},
{
callbacks: {
authorized: () => true,
},
}
);verifyAndRefreshTokenWithNextAuth(req: NextRequest, secret: string, cookiePrefix: string, serviceId: string, options: { ssoBaseURL: string; authServiceKey?: string; getNextAuthToken?: (req: NextRequest) => Promise<JWT | null> })
NextAuth 토큰을 먼저 확인하고, 없으면 자체 토큰을 확인하고 필요시 리프레시합니다.
파라미터:
req: NextRequest 객체secret: JWT 서명에 사용할 secret keycookiePrefix: 쿠키 이름 접두사serviceId: 서비스 ID (필수)options: 옵션 객체
반환값:
Promise<{ isValid: boolean; payload?: JWTPayload; error?: string }>
경로 체크
isPublicPath(pathname: string, publicPaths: string[])
경로가 공개 경로인지 확인합니다.
파라미터:
pathname: 경로publicPaths: 공개 경로 배열
반환값:
boolean
isApiPath(pathname: string)
경로가 API 경로인지 확인합니다.
파라미터:
pathname: 경로
반환값:
boolean
isProtectedApiPath(pathname: string, authApiPaths: string[])
경로가 보호된 API 경로인지 확인합니다.
파라미터:
pathname: 경로authApiPaths: 인증이 필요한 API 경로 배열
반환값:
boolean
토큰 유효성 검사
isTokenExpired(token: JWT | null)
토큰이 만료되었는지 확인합니다.
파라미터:
token: JWT 객체 또는 null
반환값:
boolean
isValidToken(token: JWT | null)
토큰이 유효한지 확인합니다.
파라미터:
token: JWT 객체 또는 null
반환값:
boolean
라이센스 검증
checkLicenseKey(licenseKey: string)
라이센스 키를 검증합니다.
파라미터:
licenseKey: 라이센스 키 (필수)
반환값:
- 없음 (유효하지 않으면 에러 발생)
사용 예시:
import { checkLicenseKey } from "@thinkingcat/auth-utils";
try {
checkLicenseKey(process.env.LICENSE_KEY!);
console.log("라이센스 키가 유효합니다");
} catch (error) {
console.error("라이센스 키 검증 실패:", error);
}리다이렉트 및 응답
createRedirectHTML(redirectPath: string, text: string)
클라이언트 리다이렉트용 HTML을 생성합니다.
파라미터:
redirectPath: 리다이렉트할 경로text: 표시할 텍스트 (필수)
반환값:
- HTML 문자열
사용 예시:
const html = createRedirectHTML("/dashboard", "myservice");
return new NextResponse(html, {
status: 200,
headers: { "Content-Type": "text/html" },
});createAuthResponse(accessToken: string, secret: string, options)
완전한 인증 세션을 생성합니다. 토큰 검증부터 쿠키 설정까지 모든 과정을 처리합니다.
파라미터:
accessToken: access tokensecret: JWT 서명에 사용할 secret keyoptions: 옵션 객체
옵션:
refreshToken: refresh token (선택)redirectPath: 리다이렉트할 경로 (선택)text: 리다이렉트 HTML에 표시할 텍스트 (선택사항, serviceId가 기본값)cookiePrefix: 쿠키 이름 접두사 (선택사항, serviceId가 기본값)isProduction: 프로덕션 환경 여부 (기본값: false)cookieDomain: 쿠키 도메인 (선택)serviceId: 서비스 ID (필수)licenseKey: 라이센스 키 (필수)
반환값:
- NextResponse 객체
사용 예시:
const secret = process.env.NEXTAUTH_SECRET!;
const response = await createAuthResponse(accessToken, secret, {
refreshToken: refreshToken,
redirectPath: "/dashboard",
text: "myservice",
cookiePrefix: "myservice",
isProduction: process.env.NODE_ENV === "production",
cookieDomain: process.env.COOKIE_DOMAIN,
serviceId: "myservice",
licenseKey: process.env.LICENSE_KEY!, // 필수
});redirectToError(req: NextRequest, errorType: string, message: string, errorPath?: string)
에러 페이지로 리다이렉트합니다.
파라미터:
req: NextRequest 객체errorType: 에러 타입message: 에러 메시지errorPath: 에러 페이지 경로 (기본값: '/error')
반환값:
- NextResponse 리다이렉트 응답
redirectToSSOLogin(req: NextRequest, serviceId: string, ssoBaseURL: string)
SSO 로그인 페이지로 리다이렉트합니다.
파라미터:
req: NextRequest 객체serviceId: 서비스 IDssoBaseURL: SSO 서버 기본 URL (필수)
반환값:
- NextResponse 리다이렉트 응답
redirectToRoleDashboard(req: NextRequest, role: string, rolePaths: Record<string, string>, defaultPath?: string)
역할별 대시보드 경로로 리다이렉트합니다.
파라미터:
req: NextRequest 객체role: 사용자 역할rolePaths: 역할별 경로 매핑defaultPath: 기본 경로 (기본값: '/admin')
반환값:
- NextResponse 리다이렉트 응답
💡 사용 시나리오
시나리오 1: 자체 토큰만 사용하는 서비스
SSO에서 받은 토큰을 자체 쿠키에만 저장하는 경우:
import { NextRequest, NextResponse } from "next/server";
import {
verifyToken,
setCustomTokens,
createRedirectHTML,
} from "@thinkingcat/auth-utils";
export async function GET(req: NextRequest) {
const tokenParam = req.nextUrl.searchParams.get("token");
if (!tokenParam) {
return NextResponse.redirect("/login");
}
const secret = process.env.NEXTAUTH_SECRET!;
const licenseKey = process.env.LICENSE_KEY!;
const tokenResult = await verifyToken(tokenParam, secret, licenseKey);
if (!tokenResult) {
return NextResponse.redirect("/login");
}
const html = createRedirectHTML("/dashboard", "myservice");
const response = new NextResponse(html, {
status: 200,
headers: { "Content-Type": "text/html" },
});
setCustomTokens(response, tokenParam, {
cookiePrefix: "myservice",
isProduction: process.env.NODE_ENV === "production",
});
return response;
}시나리오 2: NextAuth만 사용하는 서비스
NextAuth 세션만 사용하는 경우:
import { NextRequest, NextResponse } from "next/server";
import {
verifyToken,
createNextAuthJWT,
encodeNextAuthToken,
setNextAuthToken,
} from "@thinkingcat/auth-utils";
export async function GET(req: NextRequest) {
const tokenParam = req.nextUrl.searchParams.get("token");
if (!tokenParam) {
return NextResponse.redirect("/login");
}
const secret = process.env.NEXTAUTH_SECRET!;
const licenseKey = process.env.LICENSE_KEY!;
const tokenResult = await verifyToken(tokenParam, secret, licenseKey);
if (!tokenResult) {
return NextResponse.redirect("/login");
}
const { payload } = tokenResult;
const jwt = createNextAuthJWT(payload, "myservice");
const sessionToken = await encodeNextAuthToken(jwt, secret);
const response = NextResponse.redirect("/dashboard");
setNextAuthToken(response, sessionToken, {
isProduction: process.env.NODE_ENV === "production",
cookieDomain: process.env.COOKIE_DOMAIN,
});
return response;
}시나리오 3: 자체 토큰 + NextAuth 모두 사용
자체 토큰과 NextAuth 세션을 모두 사용하는 경우:
import { NextRequest, NextResponse } from "next/server";
import {
verifyToken,
extractRoleFromPayload,
createNextAuthJWT,
encodeNextAuthToken,
setCustomTokens,
setNextAuthToken,
createRedirectHTML,
} from "@thinkingcat/auth-utils";
export async function GET(req: NextRequest) {
const tokenParam = req.nextUrl.searchParams.get("token");
if (!tokenParam) {
return NextResponse.redirect("/login");
}
const secret = process.env.NEXTAUTH_SECRET!;
const licenseKey = process.env.LICENSE_KEY!;
const isProduction = process.env.NODE_ENV === "production";
const tokenResult = await verifyToken(tokenParam, secret, licenseKey);
if (!tokenResult) {
return NextResponse.redirect("/login");
}
const { payload } = tokenResult;
const role = extractRoleFromPayload(payload, "myservice", "ADMIN");
const jwt = createNextAuthJWT(payload, "myservice");
const sessionToken = await encodeNextAuthToken(jwt, secret);
const redirectPath = role === "ADMIN" ? "/admin" : "/dashboard";
const html = createRedirectHTML(redirectPath, "myservice");
const response = new NextResponse(html, {
status: 200,
headers: { "Content-Type": "text/html" },
});
setCustomTokens(response, tokenParam, {
cookiePrefix: "myservice",
isProduction,
});
setNextAuthToken(response, sessionToken, {
isProduction,
cookieDomain: process.env.COOKIE_DOMAIN,
});
return response;
}시나리오 4: 미들웨어에서 통합 사용
Next.js Middleware에서 통합 미들웨어 핸들러 사용:
import { withAuth } from "next-auth/middleware";
import { NextRequest, NextResponse } from "next/server";
import { getToken } from "next-auth/jwt";
import {
createMiddlewareConfig,
handleMiddleware,
} from "@thinkingcat/auth-utils";
const middlewareConfig = createMiddlewareConfig({
serviceId: "myservice",
publicPaths: ["/login", "/register", "/api/public"],
subscriptionRequiredPaths: ["/premium"],
roleAccessConfig: [
{
paths: ["/admin"],
role: "ADMIN",
message: "관리자만 접근할 수 있습니다.",
},
],
rolePaths: {
ADMIN: "/admin",
USER: "/dashboard",
},
});
export default withAuth(
async function middleware(req: NextRequest) {
const response = await handleMiddleware(req, middlewareConfig, {
secret: process.env.NEXTAUTH_SECRET!,
isProduction: process.env.NODE_ENV === "production",
cookieDomain: process.env.COOKIE_DOMAIN,
ssoBaseURL: process.env.SSO_BASE_URL!,
getNextAuthToken: async (req: NextRequest) => {
return await getToken({ req, secret: process.env.NEXTAUTH_SECRET! });
},
authServiceKey: process.env.AUTH_SERVICE_SECRET_KEY,
});
return response || NextResponse.next();
},
{
callbacks: {
authorized: () => true,
},
}
);
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};🔒 보안 고려사항
⚠️ 중요 사항
Secret Key 관리
NEXTAUTH_SECRET은 반드시 환경 변수로 관리하세요- 절대 코드에 하드코딩하지 마세요
- 프로덕션과 개발 환경에서 다른 secret을 사용하세요
쿠키 보안
- 프로덕션 환경에서는
isProduction: true를 설정하세요 secure: true로 설정되어 HTTPS에서만 전송됩니다httpOnly: true로 설정되어 JavaScript에서 접근할 수 없습니다
- 프로덕션 환경에서는
토큰 검증
- 모든 토큰은 사용 전에 반드시
verifyToken으로 검증하세요 - 검증 실패 시 적절한 에러 처리를 하세요
- 모든 토큰은 사용 전에 반드시
환경 변수
- 이 패키지는 환경 변수를 직접 읽지 않습니다
- 모든 값은 함수 파라미터로 전달해야 합니다
- 하드코딩된 값(서비스 ID, URL 등)이 없도록 주의하세요
📝 타입 정의
JWTPayload
JWT 토큰의 payload 타입입니다.
interface JWTPayload {
// 표준 JWT 필드
sub?: string; // Subject (사용자 ID)
id?: string; // 사용자 ID
email: string; // 이메일
name: string; // 이름
role?: string; // 역할
iat?: number; // Issued At
exp?: number; // Expiration Time
// 서비스 정보
services?: ServiceInfo[];
// 인증 상태
phoneVerified?: boolean;
emailVerified?: boolean;
smsVerified?: boolean;
// 선택적 필드
phone?: string;
isPasswordReset?: boolean;
decryptedEmail?: string;
decryptedPhone?: string;
emailHash?: string;
maskedEmail?: string;
phoneHash?: string;
maskedPhone?: string;
refreshToken?: string;
accessToken?: string;
accessTokenExpires?: number;
serviceId?: string;
// 기타 필드
[key: string]: unknown;
}ServiceInfo
interface ServiceInfo {
serviceId: string;
role: string;
joinedAt: string;
lastAccessAt?: string;
expiredAt?: string;
status: string;
}MiddlewareConfig
interface MiddlewareConfig {
serviceId: string;
publicPaths: string[];
subscriptionRequiredPaths: string[];
subscriptionExemptApiPaths: string[];
authApiPaths: string[];
roleAccessConfig: RoleAccessConfig[];
rolePaths: Record<string, string>;
systemAdminRole: string;
errorPath: string;
}MiddlewareOptions
interface MiddlewareOptions {
secret: string;
isProduction: boolean;
cookieDomain?: string;
getNextAuthToken?: (req: NextRequest) => Promise<JWT | null>;
ssoBaseURL: string;
authServiceKey?: string;
licenseKey: string;
}RoleAccessConfig
interface RoleAccessConfig {
paths: string[];
role: string;
allowedRoles?: string[];
message?: string;
}SSO API 응답 타입
SSORefreshTokenResponse
interface SSORefreshTokenResponse {
success: boolean;
accessToken?: string;
refreshToken?: string;
user?: {
id: string;
email: string;
name: string;
};
error?: string;
}SSOGetRefreshTokenResponse
interface SSOGetRefreshTokenResponse {
success: boolean;
refreshToken?: string;
error?: string;
}ResponseLike
interface ResponseLike {
cookies: {
delete(name: string): void;
set(
name: string,
value: string,
options?: {
httpOnly?: boolean;
secure?: boolean;
sameSite?: "strict" | "lax" | "none";
maxAge?: number;
path?: string;
domain?: string;
}
): void;
};
}🐛 문제 해결 (Troubleshooting)
문제 1: "Cannot find module '@thinkingcat/auth-utils'"
해결 방법:
# 패키지 재설치
npm install @thinkingcat/auth-utils
# 또는 node_modules 삭제 후 재설치
rm -rf node_modules package-lock.json
npm install문제 2: 타입 오류 "NextResponse is not assignable to ResponseLike"
해결 방법:
ResponseLike 인터페이스는 NextResponse와 호환됩니다. 타입 단언이 필요하지 않습니다. 만약 오류가 발생한다면 패키지 버전을 확인하세요.
문제 3: 토큰 검증 실패
확인 사항:
NEXTAUTH_SECRET이 올바르게 설정되었는지 확인- 토큰이 만료되지 않았는지 확인
- 토큰 형식이 올바른지 확인
문제 4: 쿠키가 설정되지 않음
확인 사항:
isProduction옵션이 올바르게 설정되었는지 확인- 브라우저 개발자 도구에서 쿠키 설정 확인
- 도메인 및 경로 설정 확인
문제 5: "ssoBaseURL is required" 에러
해결 방법:
모든 SSO 관련 함수는 ssoBaseURL을 필수 파라미터로 받습니다. 환경 변수에서 값을 가져와서 전달하세요:
const ssoBaseURL = process.env.SSO_BASE_URL!;
if (!ssoBaseURL) {
throw new Error("SSO_BASE_URL environment variable is required");
}문제 6: "cookiePrefix is required" 에러
해결 방법:
setCustomTokens와 clearAuthCookies 함수는 cookiePrefix를 필수 파라미터로 받습니다. 서비스 ID를 사용하거나 명시적으로 전달하세요:
const cookiePrefix = "myservice"; // 서비스별로 변경
setCustomTokens(response, accessToken, {
cookiePrefix, // 필수
isProduction: true,
});문제 7: "License key is required" 또는 "Invalid license key" 에러
해결 방법: 라이센스 키는 모든 함수 호출 시 필수입니다. 환경 변수에서 라이센스 키를 가져와서 전달하세요:
const licenseKey = process.env.LICENSE_KEY;
if (!licenseKey) {
throw new Error("LICENSE_KEY environment variable is required");
}
// 함수 호출 시 licenseKey 전달
const response = await handleMiddleware(req, middlewareConfig, {
secret: process.env.NEXTAUTH_SECRET!,
licenseKey, // 필수
ssoBaseURL: process.env.SSO_BASE_URL!,
// ... 기타 옵션
});📦 패키지 정보
- 패키지명:
@thinkingcat/auth-utils - 버전:
1.0.17 - 라이선스: MIT
- 저장소: npm registry
📝 변경 이력 (Changelog)
v1.0.17 (2024-11-15)
새로운 기능:
- 조건부 로깅 시스템 추가 (
debugLog,debugError) - 환경 변수
AUTH_UTILS_DEBUG지원 - 프로덕션 환경에서 로그 출력 최적화
개선 사항:
- 코드 구조 개선 (15개 기능별 섹션으로 그룹화)
- 중복 코드 제거 (
deleteNextAuthSessionCookie,clearAllAuthCookies헬퍼 추가) - 로그 메시지 간소화 및 가독성 향상
- 불필요한 주석 제거
성능 최적화:
- 프로덕션 환경에서 로그 출력 비활성화로 성능 향상
- 조건부 로깅으로 런타임 오버헤드 감소
🤝 기여 (Contributing)
이슈나 개선 사항이 있으면 GitHub 이슈를 등록해주세요.
📄 라이선스 (License)
MIT License
Made with ❤️ by ThinkingCat