npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@thinkingcat/auth-utils

v2.0.15

Published

Authentication utilities for ThinkingCat SSO services with conditional logging

Readme

@thinkingcat/auth-utils

ThinkingCat SSO 서비스를 위한 인증 유틸리티 패키지입니다. JWT 토큰 검증, NextAuth 세션 생성, 쿠키 설정, 역할 기반 접근 제어, 미들웨어 헬퍼 등의 광범위한 인증 기능을 제공합니다.

📑 목차 (Table of Contents)

📦 설치 (Installation)

npm 사용

npm install @thinkingcat/auth-utils

yarn 사용

yarn add @thinkingcat/auth-utils

pnpm 사용

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": "^2.0.2"
  }
}

설정 불필요: 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)

이 패키지는 다음 기능을 제공합니다:

  1. 토큰 검증: JWT access token 검증 및 디코딩
  2. 역할 추출: payload에서 서비스별 역할 추출
  3. NextAuth JWT 생성: NextAuth 호환 JWT 객체 생성
  4. 세션 토큰 인코딩: NextAuth 세션 토큰 생성
  5. 쿠키 설정: 자체 토큰 및 NextAuth 토큰 쿠키 설정
  6. 리다이렉트 HTML: 클라이언트 리다이렉트용 HTML 생성
  7. 완전한 인증 응답: 모든 인증 단계를 한 번에 처리
  8. 미들웨어 헬퍼: 에러 리다이렉트, 쿠키 삭제, SSO 로그인 리다이렉트 등
  9. 역할 기반 접근 제어: 경로별 역할 검증 및 접근 제어
  10. 구독 검증: 서비스 구독 상태 확인
  11. 토큰 유효성 검사: 토큰 만료 및 유효성 확인
  12. 경로 체크: 공개 경로, API 경로, 보호된 경로 확인
  13. 통합 인증 체크: NextAuth와 자체 토큰을 모두 확인하는 통합 함수
  14. 미들웨어 통합: 완전한 미들웨어 핸들러 함수 제공
  15. 설정 관리: 미들웨어 설정 생성 및 관리 함수

🔧 API 레퍼런스

토큰 검증 및 생성

verifyToken(accessToken: string, secret: string)

JWT access token을 검증하고 디코딩합니다.

파라미터:

  • accessToken: 검증할 JWT 토큰
  • secret: JWT 서명에 사용할 secret key

반환값:

  • 성공: { payload: JWTPayload }
  • 실패: null

사용 예시:

const secret = process.env.NEXTAUTH_SECRET!;
const result = await verifyToken(accessToken, secret);

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 key
  • maxAge: 토큰 유효 기간 (초, 기본값: 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 token
  • options.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: 사용자 ID
  • accessToken: access token
  • options.ssoBaseURL: SSO 서버 기본 URL (필수)
  • options.authServiceKey: 인증 서비스 키 (선택사항)

반환값:

  • string | null

verifyTokenFromSSO(accessToken: string, options: { ssoBaseURL: string; authServiceKey?: string })

SSO 서버에서 토큰을 검증합니다.

파라미터:

  • accessToken: access token
  • options.ssoBaseURL: SSO 서버 기본 URL (필수)
  • options.authServiceKey: 인증 서비스 키 (선택사항)

반환값:

  • Promise<{ isValid: boolean; payload?: JWTPayload }>

validateServiceSubscription(services: ServiceInfo[], serviceId: string, ssoBaseURL: string)

서비스 구독 상태를 확인합니다.

파라미터:

  • services: 서비스 정보 배열
  • serviceId: 서비스 ID
  • ssoBaseURL: 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 key
  • serviceId: 서비스 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 key
  • cookiePrefix: 쿠키 이름 접두사
  • 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 token
  • secret: JWT 서명에 사용할 secret key
  • options: 옵션 객체

옵션:

  • 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: 서비스 ID
  • ssoBaseURL: 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 tokenResult = await verifyToken(tokenParam, secret);

  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 tokenResult = await verifyToken(tokenParam, secret);

  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 isProduction = process.env.NODE_ENV === "production";

  const tokenResult = await verifyToken(tokenParam, secret);
  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).*)"],
};

🔒 보안 고려사항

⚠️ 중요 사항

  1. Secret Key 관리

    • NEXTAUTH_SECRET은 반드시 환경 변수로 관리하세요
    • 절대 코드에 하드코딩하지 마세요
    • 프로덕션과 개발 환경에서 다른 secret을 사용하세요
  2. 쿠키 보안

    • 프로덕션 환경에서는 isProduction: true를 설정하세요
    • secure: true로 설정되어 HTTPS에서만 전송됩니다
    • httpOnly: true로 설정되어 JavaScript에서 접근할 수 없습니다
  3. 토큰 검증

    • 모든 토큰은 사용 전에 반드시 verifyToken으로 검증하세요
    • 검증 실패 시 적절한 에러 처리를 하세요
  4. 환경 변수

    • 이 패키지는 환경 변수를 직접 읽지 않습니다
    • 모든 값은 함수 파라미터로 전달해야 합니다
    • 하드코딩된 값(서비스 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" 에러

해결 방법: setCustomTokensclearAuthCookies 함수는 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
  • 버전: 2.0.2
  • 라이선스: MIT
  • 저장소: npm registry

📝 변경 이력 (Changelog)

v2.0.2 (2025-12-24)

새로운 기능:

  • SSO 로그인 URL 경로 최적화 (/login -> /auth/login)
  • 라이센스 키(LICENSE_KEY) 필수화 및 가용성 확대
  • 타입 정의 및 미들웨어 핸들러의 안정성 강화

개선 사항:

  • verifyToken 함수에서 licenseKey 파라미터 제거 (내부 로직 최적화)
  • README.md를 최신 소스 코드 API에 맞게 전체 업데이트
  • 미들웨어 설정(MiddlewareConfig) 및 옵션(MiddlewareOptions) 상세화

v1.0.17 (2024-11-15)

🤝 기여 (Contributing)

이슈나 개선 사항이 있으면 GitHub 이슈를 등록해주세요.

📄 라이선스 (License)

MIT License


Made with ❤️ by ThinkingCat