@thinkingcat/auth-utils 1.0.6 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,25 +10,16 @@ ThinkingCat SSO 서비스를 위한 인증 유틸리티 패키지입니다. JWT
10
10
  - [🚀 빠른 시작 (Quick Start)](#-빠른-시작-quick-start)
11
11
  - [📚 주요 기능 (Features)](#-주요-기능-features)
12
12
  - [🔧 API 레퍼런스](#-api-레퍼런스)
13
- - [1. verifyToken](#1-verifytokenaccesstoken-string-secret-string)
14
- - [2. extractRoleFromPayload](#2-extractrolefrompayloadpayload-jwtpayload-serviceid-string-defaultrole-string)
15
- - [3. createNextAuthJWT](#3-createnextauthjwtpayload-jwtpayload-includeacademies-boolean)
16
- - [4. encodeNextAuthToken](#4-encodenextauthtokenjwt-jwt-secret-string-maxage-number)
17
- - [5. setCustomTokens](#5-setcustomtokensresponse-responselike-accesstoken-string-optionsorrefreshtoken-options)
18
- - [6. setNextAuthToken](#6-setnextauthtokenresponse-responselike-sessiontoken-string-options)
19
- - [7. createRedirectHTML](#7-createredirecthtmlredirectpath-string-text-string)
20
- - [8. createAuthResponse](#8-createauthresponseaccesstoken-string-secret-string-options)
13
+ - [토큰 검증 및 생성](#토큰-검증-및-생성)
14
+ - [쿠키 관리](#쿠키-관리)
15
+ - [역할 및 접근 제어](#역할-및-접근-제어)
16
+ - [SSO 통합](#sso-통합)
17
+ - [미들웨어](#미들웨어)
21
18
  - [💡 사용 시나리오](#-사용-시나리오)
22
- - [시나리오 1: 자체 토큰만 사용하는 서비스](#시나리오-1-자체-토큰만-사용하는-서비스)
23
- - [시나리오 2: NextAuth만 사용하는 서비스](#시나리오-2-nextauth만-사용하는-서비스)
24
- - [시나리오 3: 자체 토큰 + NextAuth 모두 사용 (check-on 예시)](#시나리오-3-자체-토큰--nextauth-모두-사용-check-on-예시)
25
- - [시나리오 4: Middleware에서 사용](#시나리오-4-middleware에서-사용)
26
19
  - [🔒 보안 고려사항](#-보안-고려사항)
27
20
  - [📝 타입 정의](#-타입-정의)
28
21
  - [🐛 문제 해결 (Troubleshooting)](#-문제-해결-troubleshooting)
29
22
  - [📦 패키지 정보](#-패키지-정보)
30
- - [🤝 기여 (Contributing)](#-기여-contributing)
31
- - [📄 라이선스 (License)](#-라이선스-license)
32
23
 
33
24
  ## 📦 설치 (Installation)
34
25
 
@@ -69,7 +60,7 @@ npm install @thinkingcat/auth-utils
69
60
  ```json
70
61
  {
71
62
  "dependencies": {
72
- "@thinkingcat/auth-utils": "^1.0.4"
63
+ "@thinkingcat/auth-utils": "^1.0.8"
73
64
  }
74
65
  }
75
66
  ```
@@ -94,28 +85,9 @@ const nextConfig = {
94
85
  module.exports = nextConfig;
95
86
  ```
96
87
 
97
- **왜 필요한가?**
98
-
99
- - Next.js는 기본적으로 `node_modules`의 패키지를 트랜스파일하지 않습니다
100
- - 로컬 패키지를 사용할 때는 Next.js가 소스 코드를 직접 처리해야 할 수 있습니다
101
- - `transpilePackages`는 Next.js에게 해당 패키지를 트랜스파일하라고 지시합니다
102
-
103
88
  **해결 방법 2: npm에 배포된 버전 사용 (가장 안정적)**
104
89
 
105
- 로컬 패키지 대신 npm에 배포된 버전을 사용하면 설정이 필요 없습니다:
106
-
107
- ```json
108
- {
109
- "dependencies": {
110
- "@thinkingcat/auth-utils": "^1.0.4"
111
- }
112
- }
113
- ```
114
-
115
- **권장사항**:
116
-
117
- - **프로덕션 환경**: npm에 배포된 버전 사용 (설정 불필요)
118
- - **개발 환경**: 로컬 패키지 사용 시 `transpilePackages` 추가
90
+ 로컬 패키지 대신 npm에 배포된 버전을 사용하면 설정이 필요 없습니다.
119
91
 
120
92
  ## 🚀 빠른 시작 (Quick Start)
121
93
 
@@ -123,30 +95,83 @@ module.exports = nextConfig;
123
95
 
124
96
  ```typescript
125
97
  import {
98
+ // 토큰 검증 및 생성
126
99
  verifyToken,
127
100
  extractRoleFromPayload,
128
101
  createNextAuthJWT,
129
102
  encodeNextAuthToken,
103
+
104
+ // 쿠키 관리
130
105
  setCustomTokens,
131
106
  setNextAuthToken,
107
+ clearAuthCookies,
108
+
109
+ // 리다이렉트 및 응답
132
110
  createRedirectHTML,
133
111
  createAuthResponse,
112
+ redirectToError,
113
+ redirectToSSOLogin,
114
+ redirectToRoleDashboard,
115
+
116
+ // 역할 및 접근 제어
117
+ getEffectiveRole,
118
+ hasRole,
119
+ hasAnyRole,
120
+ checkRoleAccess,
121
+ requiresSubscription,
122
+
123
+ // 경로 체크
124
+ isPublicPath,
125
+ isApiPath,
126
+ isProtectedApiPath,
127
+
128
+ // 토큰 유효성 검사
129
+ isTokenExpired,
130
+ isValidToken,
131
+
132
+ // SSO 통합
133
+ refreshSSOToken,
134
+ getRefreshTokenFromSSO,
135
+ verifyTokenFromSSO,
136
+ validateServiceSubscription,
137
+
138
+ // 미들웨어
139
+ createMiddlewareConfig,
140
+ handleMiddleware,
141
+ verifyAndRefreshTokenWithNextAuth,
142
+
143
+ // 라이센스
144
+ checkLicenseKey,
145
+
146
+ // 타입
134
147
  ServiceInfo,
135
148
  ResponseLike,
136
149
  JWTPayload,
150
+ MiddlewareConfig,
151
+ MiddlewareOptions,
137
152
  } from "@thinkingcat/auth-utils";
138
153
  ```
139
154
 
140
- ### 2. 환경 변수 설정
155
+ ### 2. 라이센스 설정 (선택사항)
141
156
 
142
- `.env.local` 파일에 다음 환경 변수를 설정하세요:
157
+ 라이센스 키를 사용하려면 `.env.local` 파일에 다음 환경 변수를 설정하세요:
143
158
 
144
159
  ```env
145
- NEXTAUTH_SECRET=your-secret-key-here
146
- NODE_ENV=production # 또는 development
147
- COOKIE_DOMAIN=.yourdomain.com # 선택사항
160
+ # 단일 라이센스 키 (가장 간단)
161
+ LICENSE_KEY=TC-checkon-XXXX-XXXX
162
+
163
+ # 또는 여러 서비스별 키 목록
164
+ LICENSE_KEYS={"checkon":"TC-checkon-XXXX-XXXX","renton":"TC-renton-YYYY-YYYY"}
148
165
  ```
149
166
 
167
+ **중요**:
168
+
169
+ - 라이센스 키는 선택사항입니다. 제공하지 않으면 라이센스 검증을 건너뜁니다.
170
+ - `LICENSE_KEY` 환경 변수를 설정하면 함수 호출 시 자동으로 사용됩니다.
171
+ - 함수 파라미터로 `licenseKey`를 전달하면 환경 변수보다 우선합니다.
172
+ - 다른 환경 변수(NEXTAUTH_SECRET, SSO_BASE_URL 등)는 이 패키지를 사용하는 애플리케이션에서 관리합니다.
173
+ - `.env.example` 파일을 참조하여 라이센스 키 설정 방법을 확인하세요.
174
+
150
175
  ## 📚 주요 기능 (Features)
151
176
 
152
177
  이 패키지는 다음 기능을 제공합니다:
@@ -164,10 +189,14 @@ COOKIE_DOMAIN=.yourdomain.com # 선택사항
164
189
  11. **토큰 유효성 검사**: 토큰 만료 및 유효성 확인
165
190
  12. **경로 체크**: 공개 경로, API 경로, 보호된 경로 확인
166
191
  13. **통합 인증 체크**: NextAuth와 자체 토큰을 모두 확인하는 통합 함수
192
+ 14. **미들웨어 통합**: 완전한 미들웨어 핸들러 함수 제공
193
+ 15. **설정 관리**: 미들웨어 설정 생성 및 관리 함수
167
194
 
168
195
  ## 🔧 API 레퍼런스
169
196
 
170
- ### 1. `verifyToken(accessToken: string, secret: string)`
197
+ ### 토큰 검증 생성
198
+
199
+ #### `verifyToken(accessToken: string, secret: string, licenseKey: string)`
171
200
 
172
201
  JWT access token을 검증하고 디코딩합니다.
173
202
 
@@ -175,6 +204,7 @@ JWT access token을 검증하고 디코딩합니다.
175
204
 
176
205
  - `accessToken`: 검증할 JWT 토큰
177
206
  - `secret`: JWT 서명에 사용할 secret key
207
+ - `licenseKey`: 라이센스 키 (필수)
178
208
 
179
209
  **반환값:**
180
210
 
@@ -185,7 +215,8 @@ JWT access token을 검증하고 디코딩합니다.
185
215
 
186
216
  ```typescript
187
217
  const secret = process.env.NEXTAUTH_SECRET!;
188
- const result = await verifyToken(accessToken, secret);
218
+ const licenseKey = process.env.LICENSE_KEY!;
219
+ const result = await verifyToken(accessToken, secret, licenseKey);
189
220
 
190
221
  if (result) {
191
222
  const { payload } = result;
@@ -196,14 +227,14 @@ if (result) {
196
227
  }
197
228
  ```
198
229
 
199
- ### 2. `extractRoleFromPayload(payload: JWTPayload, serviceId?: string, defaultRole?: string)`
230
+ #### `extractRoleFromPayload(payload: JWTPayload, serviceId: string, defaultRole?: string)`
200
231
 
201
232
  payload에서 특정 서비스의 역할을 추출합니다.
202
233
 
203
234
  **파라미터:**
204
235
 
205
236
  - `payload`: JWT payload 객체 (JWTPayload 타입)
206
- - `serviceId`: 서비스 ID (기본값: 'checkon')
237
+ - `serviceId`: 서비스 ID (필수)
207
238
  - `defaultRole`: 기본 역할 (기본값: 'ADMIN')
208
239
 
209
240
  **반환값:**
@@ -213,17 +244,18 @@ payload에서 특정 서비스의 역할을 추출합니다.
213
244
  **사용 예시:**
214
245
 
215
246
  ```typescript
216
- const role = extractRoleFromPayload(payload, "checkon", "ADMIN");
247
+ const role = extractRoleFromPayload(payload, "myservice", "ADMIN");
217
248
  // 'ADMIN', 'TEACHER', 'STUDENT' 등
218
249
  ```
219
250
 
220
- ### 3. `createNextAuthJWT(payload: JWTPayload, includeAcademies?: boolean)`
251
+ #### `createNextAuthJWT(payload: JWTPayload, serviceId: string, includeAcademies?: boolean)`
221
252
 
222
253
  NextAuth와 호환되는 JWT 객체를 생성합니다.
223
254
 
224
255
  **파라미터:**
225
256
 
226
257
  - `payload`: 원본 JWT payload (JWTPayload 타입)
258
+ - `serviceId`: 서비스 ID (필수)
227
259
  - `includeAcademies`: academies 정보 포함 여부 (기본값: false)
228
260
 
229
261
  **반환값:**
@@ -233,11 +265,11 @@ NextAuth와 호환되는 JWT 객체를 생성합니다.
233
265
  **사용 예시:**
234
266
 
235
267
  ```typescript
236
- const jwt = createNextAuthJWT(payload, true);
268
+ const jwt = createNextAuthJWT(payload, "myservice", true);
237
269
  // academies 정보가 포함된 JWT 객체
238
270
  ```
239
271
 
240
- ### 4. `encodeNextAuthToken(jwt: JWT, secret: string, maxAge?: number)`
272
+ #### `encodeNextAuthToken(jwt: JWT, secret: string, maxAge?: number)`
241
273
 
242
274
  NextAuth JWT 객체를 인코딩된 세션 토큰으로 변환합니다.
243
275
 
@@ -255,11 +287,13 @@ NextAuth JWT 객체를 인코딩된 세션 토큰으로 변환합니다.
255
287
 
256
288
  ```typescript
257
289
  const secret = process.env.NEXTAUTH_SECRET!;
258
- const jwt = createNextAuthJWT(payload);
290
+ const jwt = createNextAuthJWT(payload, "myservice");
259
291
  const sessionToken = await encodeNextAuthToken(jwt, secret);
260
292
  ```
261
293
 
262
- ### 5. `setCustomTokens(response: ResponseLike, accessToken: string, optionsOrRefreshToken?, options?)`
294
+ ### 쿠키 관리
295
+
296
+ #### `setCustomTokens(response: ResponseLike, accessToken: string, optionsOrRefreshToken?, options?)`
263
297
 
264
298
  자체 토큰(access token, refresh token)만 쿠키에 설정합니다.
265
299
 
@@ -274,46 +308,32 @@ const sessionToken = await encodeNextAuthToken(jwt, secret);
274
308
 
275
309
  **옵션:**
276
310
 
277
- - `cookiePrefix`: 쿠키 이름 접두사 (기본값: 'checkon')
311
+ - `cookiePrefix`: 쿠키 이름 접두사 (필수)
278
312
  - `isProduction`: 프로덕션 환경 여부 (기본값: false)
279
313
  - `refreshToken`: refresh token (옵션 객체 내부에 포함 가능)
280
314
 
281
315
  **사용 예시:**
282
316
 
283
- **방법 1: refreshToken이 있는 경우**
284
-
285
317
  ```typescript
318
+ // 방법 1: refreshToken이 있는 경우
286
319
  setCustomTokens(response, accessToken, refreshToken, {
287
320
  cookiePrefix: "myservice",
288
321
  isProduction: process.env.NODE_ENV === "production",
289
322
  });
290
- ```
291
-
292
- **방법 2: refreshToken이 없는 경우**
293
323
 
294
- ```typescript
324
+ // 방법 2: refreshToken이 없는 경우
295
325
  setCustomTokens(response, accessToken, {
296
326
  cookiePrefix: "myservice",
297
327
  isProduction: process.env.NODE_ENV === "production",
298
328
  });
299
329
  ```
300
330
 
301
- **방법 3: 옵션 객체에 refreshToken 포함**
302
-
303
- ```typescript
304
- setCustomTokens(response, accessToken, {
305
- refreshToken: refreshToken,
306
- cookiePrefix: "myservice",
307
- isProduction: true,
308
- });
309
- ```
310
-
311
331
  **설정되는 쿠키:**
312
332
 
313
333
  - `{cookiePrefix}_access_token`: 15분 유효
314
334
  - `{cookiePrefix}_refresh_token`: 30일 유효 (있는 경우)
315
335
 
316
- ### 6. `setNextAuthToken(response: ResponseLike, sessionToken: string, options?)`
336
+ #### `setNextAuthToken(response: ResponseLike, sessionToken: string, options?)`
317
337
 
318
338
  NextAuth 세션 토큰만 쿠키에 설정합니다.
319
339
 
@@ -343,14 +363,384 @@ setNextAuthToken(response, sessionToken, {
343
363
  - 프로덕션: `__Secure-next-auth.session-token`
344
364
  - 유효 기간: 30일
345
365
 
346
- ### 7. `createRedirectHTML(redirectPath: string, text?: string)`
366
+ #### `clearAuthCookies(response: NextResponse, cookiePrefix: string)`
367
+
368
+ 인증 쿠키를 삭제합니다.
369
+
370
+ **파라미터:**
371
+
372
+ - `response`: NextResponse 객체
373
+ - `cookiePrefix`: 쿠키 이름 접두사 (필수)
374
+
375
+ **사용 예시:**
376
+
377
+ ```typescript
378
+ const response = NextResponse.redirect("/login");
379
+ clearAuthCookies(response, "myservice");
380
+ return response;
381
+ ```
382
+
383
+ ### 역할 및 접근 제어
384
+
385
+ #### `getEffectiveRole(payload: JWTPayload, serviceId: string, defaultRole?: string)`
386
+
387
+ payload에서 유효한 역할을 가져옵니다.
388
+
389
+ **파라미터:**
390
+
391
+ - `payload`: JWT payload 객체
392
+ - `serviceId`: 서비스 ID (필수)
393
+ - `defaultRole`: 기본 역할 (기본값: 'ADMIN')
394
+
395
+ **반환값:**
396
+
397
+ - 역할 문자열
398
+
399
+ #### `hasRole(payload: JWTPayload, serviceId: string, role: string)`
400
+
401
+ payload에 특정 역할이 있는지 확인합니다.
402
+
403
+ **파라미터:**
404
+
405
+ - `payload`: JWT payload 객체
406
+ - `serviceId`: 서비스 ID (필수)
407
+ - `role`: 확인할 역할
408
+
409
+ **반환값:**
410
+
411
+ - `boolean`
412
+
413
+ #### `hasAnyRole(payload: JWTPayload, serviceId: string, roles: string[])`
414
+
415
+ payload에 여러 역할 중 하나라도 있는지 확인합니다.
416
+
417
+ **파라미터:**
418
+
419
+ - `payload`: JWT payload 객체
420
+ - `serviceId`: 서비스 ID (필수)
421
+ - `roles`: 확인할 역할 배열
422
+
423
+ **반환값:**
424
+
425
+ - `boolean`
426
+
427
+ #### `checkRoleAccess(pathname: string, role: string, config: RoleAccessConfig[])`
428
+
429
+ 경로에 대한 역할 접근 권한을 확인합니다.
430
+
431
+ **파라미터:**
432
+
433
+ - `pathname`: 경로
434
+ - `role`: 사용자 역할
435
+ - `config`: 역할 접근 설정 배열
436
+
437
+ **반환값:**
438
+
439
+ - `{ allowed: boolean; message?: string }`
440
+
441
+ #### `requiresSubscription(pathname: string, role: string, subscriptionRequiredPaths: string[], systemAdminRole?: string)`
442
+
443
+ 경로가 구독이 필요한지 확인합니다.
444
+
445
+ **파라미터:**
446
+
447
+ - `pathname`: 경로
448
+ - `role`: 사용자 역할
449
+ - `subscriptionRequiredPaths`: 구독이 필요한 경로 배열
450
+ - `systemAdminRole`: 시스템 관리자 역할 (선택사항)
451
+
452
+ **반환값:**
453
+
454
+ - `boolean`
455
+
456
+ ### SSO 통합
457
+
458
+ #### `refreshSSOToken(refreshToken: string, options: { ssoBaseURL: string; authServiceKey?: string })`
459
+
460
+ SSO 서버에서 refresh token을 사용하여 새로운 access token을 발급받습니다.
461
+
462
+ **파라미터:**
463
+
464
+ - `refreshToken`: refresh token
465
+ - `options.ssoBaseURL`: SSO 서버 기본 URL (필수)
466
+ - `options.authServiceKey`: 인증 서비스 키 (선택사항)
467
+
468
+ **반환값:**
469
+
470
+ - `SSORefreshTokenResponse`
471
+
472
+ **사용 예시:**
473
+
474
+ ```typescript
475
+ const result = await refreshSSOToken(refreshToken, {
476
+ ssoBaseURL: process.env.SSO_BASE_URL!,
477
+ authServiceKey: process.env.AUTH_SERVICE_SECRET_KEY,
478
+ });
479
+ ```
480
+
481
+ #### `getRefreshTokenFromSSO(userId: string, accessToken: string, options: { ssoBaseURL: string; authServiceKey?: string })`
482
+
483
+ SSO 서버에서 사용자의 refresh token을 가져옵니다.
484
+
485
+ **파라미터:**
486
+
487
+ - `userId`: 사용자 ID
488
+ - `accessToken`: access token
489
+ - `options.ssoBaseURL`: SSO 서버 기본 URL (필수)
490
+ - `options.authServiceKey`: 인증 서비스 키 (선택사항)
491
+
492
+ **반환값:**
493
+
494
+ - `string | null`
495
+
496
+ #### `verifyTokenFromSSO(accessToken: string, options: { ssoBaseURL: string; authServiceKey?: string })`
497
+
498
+ SSO 서버에서 토큰을 검증합니다.
499
+
500
+ **파라미터:**
501
+
502
+ - `accessToken`: access token
503
+ - `options.ssoBaseURL`: SSO 서버 기본 URL (필수)
504
+ - `options.authServiceKey`: 인증 서비스 키 (선택사항)
505
+
506
+ **반환값:**
507
+
508
+ - `Promise<{ isValid: boolean; payload?: JWTPayload }>`
509
+
510
+ #### `validateServiceSubscription(services: ServiceInfo[], serviceId: string, ssoBaseURL: string)`
511
+
512
+ 서비스 구독 상태를 확인합니다.
513
+
514
+ **파라미터:**
515
+
516
+ - `services`: 서비스 정보 배열
517
+ - `serviceId`: 서비스 ID
518
+ - `ssoBaseURL`: SSO 서버 기본 URL (필수)
519
+
520
+ **반환값:**
521
+
522
+ - `{ isValid: boolean; redirectUrl?: string }`
523
+
524
+ ### 미들웨어
525
+
526
+ #### `createMiddlewareConfig(config: Partial<MiddlewareConfig> & { serviceId: string }, defaults?)`
527
+
528
+ 미들웨어 설정을 생성합니다.
529
+
530
+ **파라미터:**
531
+
532
+ - `config`: 미들웨어 설정 객체 (serviceId 필수)
533
+ - `defaults`: 기본 설정값 (선택사항)
534
+
535
+ **반환값:**
536
+
537
+ - 완전한 미들웨어 설정 객체
538
+
539
+ **사용 예시:**
540
+
541
+ ```typescript
542
+ import { createMiddlewareConfig } from "@thinkingcat/auth-utils";
543
+
544
+ const middlewareConfig = createMiddlewareConfig({
545
+ serviceId: "myservice",
546
+ publicPaths: ["/login", "/register"],
547
+ roleAccessConfig: [
548
+ {
549
+ paths: ["/admin"],
550
+ role: "ADMIN",
551
+ message: "관리자만 접근할 수 있습니다.",
552
+ },
553
+ ],
554
+ });
555
+ ```
556
+
557
+ #### `handleMiddleware(req: NextRequest, config: MiddlewareConfig, options: MiddlewareOptions)`
558
+
559
+ 통합 미들웨어 핸들러 함수입니다. 모든 인증, 권한, 구독 체크를 포함합니다.
560
+
561
+ **파라미터:**
562
+
563
+ - `req`: NextRequest 객체
564
+ - `config`: 미들웨어 설정 (createMiddlewareConfig로 생성)
565
+ - `options`: 미들웨어 실행 옵션
566
+
567
+ **옵션:**
568
+
569
+ - `secret`: NextAuth Secret (필수)
570
+ - `isProduction`: 프로덕션 환경 여부 (필수)
571
+ - `cookieDomain`: 쿠키 도메인 (선택사항)
572
+ - `getNextAuthToken`: NextAuth 토큰을 가져오는 함수 (선택사항)
573
+ - `ssoBaseURL`: SSO 서버 기본 URL (필수)
574
+ - `authServiceKey`: 인증 서비스 키 (선택사항)
575
+
576
+ **반환값:**
577
+
578
+ - NextResponse 또는 null (다음 미들웨어로 진행)
579
+
580
+ **사용 예시:**
581
+
582
+ ```typescript
583
+ import { withAuth } from "next-auth/middleware";
584
+ import { NextRequest, NextResponse } from "next/server";
585
+ import { getToken } from "next-auth/jwt";
586
+ import {
587
+ createMiddlewareConfig,
588
+ handleMiddleware,
589
+ } from "@thinkingcat/auth-utils";
590
+
591
+ const middlewareConfig = createMiddlewareConfig({
592
+ serviceId: "myservice",
593
+ publicPaths: ["/login", "/register"],
594
+ roleAccessConfig: [
595
+ {
596
+ paths: ["/admin"],
597
+ role: "ADMIN",
598
+ message: "관리자만 접근할 수 있습니다.",
599
+ },
600
+ ],
601
+ });
602
+
603
+ export default withAuth(
604
+ async function middleware(req: NextRequest) {
605
+ const response = await handleMiddleware(req, middlewareConfig, {
606
+ secret: process.env.NEXTAUTH_SECRET!,
607
+ isProduction: process.env.NODE_ENV === "production",
608
+ cookieDomain: process.env.COOKIE_DOMAIN,
609
+ ssoBaseURL: process.env.SSO_BASE_URL!,
610
+ getNextAuthToken: async (req: NextRequest) => {
611
+ return await getToken({ req, secret: process.env.NEXTAUTH_SECRET! });
612
+ },
613
+ authServiceKey: process.env.AUTH_SERVICE_SECRET_KEY,
614
+ });
615
+ return response || NextResponse.next();
616
+ },
617
+ {
618
+ callbacks: {
619
+ authorized: () => true,
620
+ },
621
+ }
622
+ );
623
+ ```
624
+
625
+ #### `verifyAndRefreshTokenWithNextAuth(req: NextRequest, secret: string, cookiePrefix: string, serviceId: string, options: { ssoBaseURL: string; authServiceKey?: string; getNextAuthToken?: (req: NextRequest) => Promise<JWT | null> })`
626
+
627
+ NextAuth 토큰을 먼저 확인하고, 없으면 자체 토큰을 확인하고 필요시 리프레시합니다.
628
+
629
+ **파라미터:**
630
+
631
+ - `req`: NextRequest 객체
632
+ - `secret`: JWT 서명에 사용할 secret key
633
+ - `cookiePrefix`: 쿠키 이름 접두사
634
+ - `serviceId`: 서비스 ID (필수)
635
+ - `options`: 옵션 객체
636
+
637
+ **반환값:**
638
+
639
+ - `Promise<{ isValid: boolean; payload?: JWTPayload; error?: string }>`
640
+
641
+ ### 경로 체크
642
+
643
+ #### `isPublicPath(pathname: string, publicPaths: string[])`
644
+
645
+ 경로가 공개 경로인지 확인합니다.
646
+
647
+ **파라미터:**
648
+
649
+ - `pathname`: 경로
650
+ - `publicPaths`: 공개 경로 배열
651
+
652
+ **반환값:**
653
+
654
+ - `boolean`
655
+
656
+ #### `isApiPath(pathname: string)`
657
+
658
+ 경로가 API 경로인지 확인합니다.
659
+
660
+ **파라미터:**
661
+
662
+ - `pathname`: 경로
663
+
664
+ **반환값:**
665
+
666
+ - `boolean`
667
+
668
+ #### `isProtectedApiPath(pathname: string, authApiPaths: string[])`
669
+
670
+ 경로가 보호된 API 경로인지 확인합니다.
671
+
672
+ **파라미터:**
673
+
674
+ - `pathname`: 경로
675
+ - `authApiPaths`: 인증이 필요한 API 경로 배열
676
+
677
+ **반환값:**
678
+
679
+ - `boolean`
680
+
681
+ ### 토큰 유효성 검사
682
+
683
+ #### `isTokenExpired(token: JWT | null)`
684
+
685
+ 토큰이 만료되었는지 확인합니다.
686
+
687
+ **파라미터:**
688
+
689
+ - `token`: JWT 객체 또는 null
690
+
691
+ **반환값:**
692
+
693
+ - `boolean`
694
+
695
+ #### `isValidToken(token: JWT | null)`
696
+
697
+ 토큰이 유효한지 확인합니다.
698
+
699
+ **파라미터:**
700
+
701
+ - `token`: JWT 객체 또는 null
702
+
703
+ **반환값:**
704
+
705
+ - `boolean`
706
+
707
+ ### 라이센스 검증
708
+
709
+ #### `checkLicenseKey(licenseKey: string)`
710
+
711
+ 라이센스 키를 검증합니다.
712
+
713
+ **파라미터:**
714
+
715
+ - `licenseKey`: 라이센스 키 (필수)
716
+
717
+ **반환값:**
718
+
719
+ - 없음 (유효하지 않으면 에러 발생)
720
+
721
+ **사용 예시:**
722
+
723
+ ```typescript
724
+ import { checkLicenseKey } from "@thinkingcat/auth-utils";
725
+
726
+ try {
727
+ checkLicenseKey(process.env.LICENSE_KEY!);
728
+ console.log("라이센스 키가 유효합니다");
729
+ } catch (error) {
730
+ console.error("라이센스 키 검증 실패:", error);
731
+ }
732
+ ```
733
+
734
+ ### 리다이렉트 및 응답
735
+
736
+ #### `createRedirectHTML(redirectPath: string, text: string)`
347
737
 
348
738
  클라이언트 리다이렉트용 HTML을 생성합니다.
349
739
 
350
740
  **파라미터:**
351
741
 
352
742
  - `redirectPath`: 리다이렉트할 경로
353
- - `text`: 표시할 텍스트 (기본값: 'checkon')
743
+ - `text`: 표시할 텍스트 (필수)
354
744
 
355
745
  **반환값:**
356
746
 
@@ -359,14 +749,14 @@ setNextAuthToken(response, sessionToken, {
359
749
  **사용 예시:**
360
750
 
361
751
  ```typescript
362
- const html = createRedirectHTML("/dashboard", "checkon");
752
+ const html = createRedirectHTML("/dashboard", "myservice");
363
753
  return new NextResponse(html, {
364
754
  status: 200,
365
755
  headers: { "Content-Type": "text/html" },
366
756
  });
367
757
  ```
368
758
 
369
- ### 8. `createAuthResponse(accessToken: string, secret: string, options?)`
759
+ #### `createAuthResponse(accessToken: string, secret: string, options)`
370
760
 
371
761
  완전한 인증 세션을 생성합니다. 토큰 검증부터 쿠키 설정까지 모든 과정을 처리합니다.
372
762
 
@@ -380,10 +770,11 @@ return new NextResponse(html, {
380
770
 
381
771
  - `refreshToken`: refresh token (선택)
382
772
  - `redirectPath`: 리다이렉트할 경로 (선택)
383
- - `text`: 리다이렉트 HTML에 표시할 텍스트 (기본값: 'checkon')
384
- - `cookiePrefix`: 쿠키 이름 접두사 (기본값: 'checkon')
773
+ - `text`: 리다이렉트 HTML에 표시할 텍스트 (선택사항, serviceId가 기본값)
774
+ - `cookiePrefix`: 쿠키 이름 접두사 (선택사항, serviceId가 기본값)
385
775
  - `isProduction`: 프로덕션 환경 여부 (기본값: false)
386
776
  - `cookieDomain`: 쿠키 도메인 (선택)
777
+ - `serviceId`: 서비스 ID (필수)
387
778
 
388
779
  **반환값:**
389
780
 
@@ -396,55 +787,86 @@ const secret = process.env.NEXTAUTH_SECRET!;
396
787
  const response = await createAuthResponse(accessToken, secret, {
397
788
  refreshToken: refreshToken,
398
789
  redirectPath: "/dashboard",
399
- text: "checkon",
400
- cookiePrefix: "checkon",
790
+ text: "myservice",
791
+ cookiePrefix: "myservice",
401
792
  isProduction: process.env.NODE_ENV === "production",
402
793
  cookieDomain: process.env.COOKIE_DOMAIN,
794
+ serviceId: "myservice", // 필수
403
795
  });
404
796
  ```
405
797
 
798
+ #### `redirectToError(req: NextRequest, errorType: string, message: string, errorPath?: string)`
799
+
800
+ 에러 페이지로 리다이렉트합니다.
801
+
802
+ **파라미터:**
803
+
804
+ - `req`: NextRequest 객체
805
+ - `errorType`: 에러 타입
806
+ - `message`: 에러 메시지
807
+ - `errorPath`: 에러 페이지 경로 (기본값: '/error')
808
+
809
+ **반환값:**
810
+
811
+ - NextResponse 리다이렉트 응답
812
+
813
+ #### `redirectToSSOLogin(req: NextRequest, serviceId: string, ssoBaseURL: string)`
814
+
815
+ SSO 로그인 페이지로 리다이렉트합니다.
816
+
817
+ **파라미터:**
818
+
819
+ - `req`: NextRequest 객체
820
+ - `serviceId`: 서비스 ID
821
+ - `ssoBaseURL`: SSO 서버 기본 URL (필수)
822
+
823
+ **반환값:**
824
+
825
+ - NextResponse 리다이렉트 응답
826
+
827
+ #### `redirectToRoleDashboard(req: NextRequest, role: string, rolePaths: Record<string, string>, defaultPath?: string)`
828
+
829
+ 역할별 대시보드 경로로 리다이렉트합니다.
830
+
831
+ **파라미터:**
832
+
833
+ - `req`: NextRequest 객체
834
+ - `role`: 사용자 역할
835
+ - `rolePaths`: 역할별 경로 매핑
836
+ - `defaultPath`: 기본 경로 (기본값: '/admin')
837
+
838
+ **반환값:**
839
+
840
+ - NextResponse 리다이렉트 응답
841
+
406
842
  ## 💡 사용 시나리오
407
843
 
408
844
  ### 시나리오 1: 자체 토큰만 사용하는 서비스
409
845
 
410
846
  SSO에서 받은 토큰을 자체 쿠키에만 저장하는 경우:
411
847
 
412
- #### 1-1. URL 파라미터로 토큰을 받는 경우 (초기 로그인)
413
-
414
848
  ```typescript
415
849
  import { NextRequest, NextResponse } from "next/server";
416
850
  import {
417
851
  verifyToken,
418
852
  setCustomTokens,
419
853
  createRedirectHTML,
420
- JWTPayload,
421
854
  } from "@thinkingcat/auth-utils";
422
855
 
423
856
  export async function GET(req: NextRequest) {
424
- // URL 파라미터에서 토큰 가져오기 (초기 로그인 시)
425
857
  const tokenParam = req.nextUrl.searchParams.get("token");
426
-
427
858
  if (!tokenParam) {
428
859
  return NextResponse.redirect("/login");
429
860
  }
430
861
 
431
- // 1. 토큰 검증 (중요: 모든 토큰은 사용 전에 반드시 검증해야 합니다)
432
862
  const secret = process.env.NEXTAUTH_SECRET!;
433
- const tokenResult = await verifyToken(tokenParam, secret);
863
+ const licenseKey = process.env.LICENSE_KEY!;
864
+ const tokenResult = await verifyToken(tokenParam, secret, licenseKey);
434
865
 
435
- // 2. 검증 실패 시 로그인 페이지로 리다이렉트
436
866
  if (!tokenResult) {
437
- console.error("Token verification failed");
438
867
  return NextResponse.redirect("/login");
439
868
  }
440
869
 
441
- // 3. 검증된 payload 확인 (선택적: 사용자 정보가 필요한 경우)
442
- const { payload } = tokenResult;
443
- console.log("Authenticated user:", payload.email);
444
-
445
- // 4. 검증된 토큰을 쿠키에 저장
446
- // 주의: verifyToken이 성공했다는 것은 토큰이 유효하고 서명이 올바르다는 의미입니다
447
- // 따라서 원본 토큰을 안전하게 쿠키에 저장할 수 있습니다
448
870
  const html = createRedirectHTML("/dashboard", "myservice");
449
871
  const response = new NextResponse(html, {
450
872
  status: 200,
@@ -460,71 +882,6 @@ export async function GET(req: NextRequest) {
460
882
  }
461
883
  ```
462
884
 
463
- #### 1-2. 쿠키에서 토큰을 읽어서 검증하는 경우 (이미 로그인된 상태)
464
-
465
- ```typescript
466
- import { NextRequest, NextResponse } from "next/server";
467
- import { verifyToken, JWTPayload } from "@thinkingcat/auth-utils";
468
-
469
- export async function GET(req: NextRequest) {
470
- const secret = process.env.NEXTAUTH_SECRET!;
471
-
472
- // 1. 쿠키에서 토큰 가져오기 (이미 로그인된 경우)
473
- const cookieToken = req.cookies.get("myservice_access_token")?.value;
474
-
475
- if (!cookieToken) {
476
- // 토큰이 없으면 로그인 페이지로 리다이렉트
477
- return NextResponse.redirect("/login");
478
- }
479
-
480
- // 2. 쿠키의 토큰 검증 (중요: 쿠키의 토큰도 반드시 검증해야 합니다)
481
- const tokenResult = await verifyToken(cookieToken, secret);
482
-
483
- if (!tokenResult) {
484
- // 검증 실패 시 쿠키 삭제하고 로그인 페이지로 리다이렉트
485
- const response = NextResponse.redirect("/login");
486
- response.cookies.delete("myservice_access_token");
487
- response.cookies.delete("myservice_refresh_token");
488
- return response;
489
- }
490
-
491
- // 3. 검증 성공 - 사용자 정보 사용
492
- const { payload } = tokenResult;
493
- console.log("Authenticated user:", payload.email);
494
-
495
- // 4. 정상 응답 반환
496
- return NextResponse.json({
497
- success: true,
498
- user: {
499
- email: payload.email,
500
- name: payload.name,
501
- },
502
- });
503
- }
504
- ```
505
-
506
- **토큰 검증 로직 설명:**
507
-
508
- 1. **`verifyToken(token, secret)`**:
509
-
510
- - JWT 토큰의 서명을 검증합니다
511
- - 토큰이 만료되지 않았는지 확인합니다
512
- - 토큰의 형식이 올바른지 확인합니다
513
- - 검증 성공 시 `{ payload: JWTPayload }` 반환
514
- - 검증 실패 시 `null` 반환
515
-
516
- 2. **토큰 소스별 처리**:
517
-
518
- - **URL 파라미터**: 초기 로그인 시 SSO에서 받은 토큰을 검증 후 쿠키에 저장
519
- - **쿠키**: 이미 로그인된 상태에서 쿠키의 토큰을 검증하여 사용
520
- - **중요**: 쿠키에 저장된 토큰도 매번 검증해야 합니다 (토큰이 만료되었을 수 있음)
521
-
522
- 3. **검증 후 처리**:
523
- - 검증이 성공하면 토큰이 유효하다는 것이 보장됩니다
524
- - URL 파라미터로 받은 토큰은 검증 후 쿠키에 저장할 수 있습니다
525
- - 쿠키에서 읽은 토큰은 검증 후 사용할 수 있습니다
526
- - 필요시 `payload`에서 사용자 정보를 추출하여 사용할 수 있습니다
527
-
528
885
  ### 시나리오 2: NextAuth만 사용하는 서비스
529
886
 
530
887
  NextAuth 세션만 사용하는 경우:
@@ -540,20 +897,20 @@ import {
540
897
 
541
898
  export async function GET(req: NextRequest) {
542
899
  const tokenParam = req.nextUrl.searchParams.get("token");
543
-
544
900
  if (!tokenParam) {
545
901
  return NextResponse.redirect("/login");
546
902
  }
547
903
 
548
904
  const secret = process.env.NEXTAUTH_SECRET!;
549
- const tokenResult = await verifyToken(tokenParam, secret);
905
+ const licenseKey = process.env.LICENSE_KEY!;
906
+ const tokenResult = await verifyToken(tokenParam, secret, licenseKey);
550
907
 
551
908
  if (!tokenResult) {
552
909
  return NextResponse.redirect("/login");
553
910
  }
554
911
 
555
912
  const { payload } = tokenResult;
556
- const jwt = createNextAuthJWT(payload);
913
+ const jwt = createNextAuthJWT(payload, "myservice");
557
914
  const sessionToken = await encodeNextAuthToken(jwt, secret);
558
915
 
559
916
  const response = NextResponse.redirect("/dashboard");
@@ -566,7 +923,7 @@ export async function GET(req: NextRequest) {
566
923
  }
567
924
  ```
568
925
 
569
- ### 시나리오 3: 자체 토큰 + NextAuth 모두 사용 (check-on 예시)
926
+ ### 시나리오 3: 자체 토큰 + NextAuth 모두 사용
570
927
 
571
928
  자체 토큰과 NextAuth 세션을 모두 사용하는 경우:
572
929
 
@@ -584,49 +941,38 @@ import {
584
941
 
585
942
  export async function GET(req: NextRequest) {
586
943
  const tokenParam = req.nextUrl.searchParams.get("token");
587
-
588
944
  if (!tokenParam) {
589
945
  return NextResponse.redirect("/login");
590
946
  }
591
947
 
592
948
  const secret = process.env.NEXTAUTH_SECRET!;
949
+ const licenseKey = process.env.LICENSE_KEY!;
593
950
  const isProduction = process.env.NODE_ENV === "production";
594
951
 
595
- // 1. 토큰 검증
596
- const tokenResult = await verifyToken(tokenParam, secret);
952
+ const tokenResult = await verifyToken(tokenParam, secret, licenseKey);
597
953
  if (!tokenResult) {
598
954
  return NextResponse.redirect("/login");
599
955
  }
600
956
 
601
957
  const { payload } = tokenResult;
958
+ const role = extractRoleFromPayload(payload, "myservice", "ADMIN");
602
959
 
603
- // 2. 역할 추출
604
- const role = extractRoleFromPayload(payload, "checkon", "ADMIN");
605
-
606
- // 3. Refresh token 가져오기 (SSO API 호출 등)
607
- let refreshToken = "";
608
- // ... refreshToken 가져오는 로직 ...
609
-
610
- // 4. NextAuth JWT 생성 및 인코딩
611
- const jwt = createNextAuthJWT(payload);
960
+ const jwt = createNextAuthJWT(payload, "myservice");
612
961
  const sessionToken = await encodeNextAuthToken(jwt, secret);
613
962
 
614
- // 5. 역할별 리다이렉트 경로 결정
615
963
  const redirectPath = role === "ADMIN" ? "/admin" : "/dashboard";
616
- const html = createRedirectHTML(redirectPath, "checkon");
964
+ const html = createRedirectHTML(redirectPath, "myservice");
617
965
 
618
966
  const response = new NextResponse(html, {
619
967
  status: 200,
620
968
  headers: { "Content-Type": "text/html" },
621
969
  });
622
970
 
623
- // 6. 자체 토큰 설정
624
- setCustomTokens(response, tokenParam, refreshToken, {
625
- cookiePrefix: "checkon",
971
+ setCustomTokens(response, tokenParam, {
972
+ cookiePrefix: "myservice",
626
973
  isProduction,
627
974
  });
628
975
 
629
- // 7. NextAuth 토큰 설정
630
976
  setNextAuthToken(response, sessionToken, {
631
977
  isProduction,
632
978
  cookieDomain: process.env.COOKIE_DOMAIN,
@@ -636,71 +982,60 @@ export async function GET(req: NextRequest) {
636
982
  }
637
983
  ```
638
984
 
639
- ### 시나리오 4: Middleware에서 사용
985
+ ### 시나리오 4: 미들웨어에서 통합 사용
640
986
 
641
- Next.js Middleware에서 토큰 검증 리프레시:
987
+ Next.js Middleware에서 통합 미들웨어 핸들러 사용:
642
988
 
643
989
  ```typescript
990
+ import { withAuth } from "next-auth/middleware";
644
991
  import { NextRequest, NextResponse } from "next/server";
992
+ import { getToken } from "next-auth/jwt";
645
993
  import {
646
- verifyToken,
647
- setCustomTokens,
648
- setNextAuthToken,
649
- createNextAuthJWT,
650
- encodeNextAuthToken,
994
+ createMiddlewareConfig,
995
+ handleMiddleware,
651
996
  } from "@thinkingcat/auth-utils";
652
997
 
653
- export async function middleware(req: NextRequest) {
654
- const secret = process.env.NEXTAUTH_SECRET!;
655
- const isProduction = process.env.NODE_ENV === "production";
656
-
657
- // 1. 자체 토큰 확인
658
- const accessToken = req.cookies.get("checkon_access_token")?.value;
659
-
660
- if (accessToken) {
661
- const tokenResult = await verifyToken(accessToken, secret);
662
-
663
- if (tokenResult) {
664
- // 토큰이 유효함
665
- return NextResponse.next();
666
- }
667
-
668
- // 토큰이 만료됨 - 리프레시 시도
669
- const refreshToken = req.cookies.get("checkon_refresh_token")?.value;
670
-
671
- if (refreshToken) {
672
- // SSO API로 토큰 리프레시
673
- // ... refreshSSOToken 호출 ...
674
-
675
- if (newAccessToken) {
676
- const newTokenResult = await verifyToken(newAccessToken, secret);
677
-
678
- if (newTokenResult) {
679
- const { payload } = newTokenResult;
680
- const jwt = createNextAuthJWT(payload);
681
- const sessionToken = await encodeNextAuthToken(jwt, secret);
682
-
683
- const response = NextResponse.next();
684
-
685
- setCustomTokens(response, newAccessToken, {
686
- cookiePrefix: "checkon",
687
- isProduction,
688
- });
689
-
690
- setNextAuthToken(response, sessionToken, {
691
- isProduction,
692
- cookieDomain: process.env.COOKIE_DOMAIN,
693
- });
998
+ const middlewareConfig = createMiddlewareConfig({
999
+ serviceId: "myservice",
1000
+ publicPaths: ["/login", "/register", "/api/public"],
1001
+ subscriptionRequiredPaths: ["/premium"],
1002
+ roleAccessConfig: [
1003
+ {
1004
+ paths: ["/admin"],
1005
+ role: "ADMIN",
1006
+ message: "관리자만 접근할 있습니다.",
1007
+ },
1008
+ ],
1009
+ rolePaths: {
1010
+ ADMIN: "/admin",
1011
+ USER: "/dashboard",
1012
+ },
1013
+ });
694
1014
 
695
- return response;
696
- }
697
- }
698
- }
1015
+ export default withAuth(
1016
+ async function middleware(req: NextRequest) {
1017
+ const response = await handleMiddleware(req, middlewareConfig, {
1018
+ secret: process.env.NEXTAUTH_SECRET!,
1019
+ isProduction: process.env.NODE_ENV === "production",
1020
+ cookieDomain: process.env.COOKIE_DOMAIN,
1021
+ ssoBaseURL: process.env.SSO_BASE_URL!,
1022
+ getNextAuthToken: async (req: NextRequest) => {
1023
+ return await getToken({ req, secret: process.env.NEXTAUTH_SECRET! });
1024
+ },
1025
+ authServiceKey: process.env.AUTH_SERVICE_SECRET_KEY,
1026
+ });
1027
+ return response || NextResponse.next();
1028
+ },
1029
+ {
1030
+ callbacks: {
1031
+ authorized: () => true,
1032
+ },
699
1033
  }
1034
+ );
700
1035
 
701
- // 인증 실패 - 로그인 페이지로 리다이렉트
702
- return NextResponse.redirect(new URL("/login", req.url));
703
- }
1036
+ export const config = {
1037
+ matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
1038
+ };
704
1039
  ```
705
1040
 
706
1041
  ## 🔒 보안 고려사항
@@ -720,9 +1055,15 @@ export async function middleware(req: NextRequest) {
720
1055
  - `httpOnly: true`로 설정되어 JavaScript에서 접근할 수 없습니다
721
1056
 
722
1057
  3. **토큰 검증**
1058
+
723
1059
  - 모든 토큰은 사용 전에 반드시 `verifyToken`으로 검증하세요
724
1060
  - 검증 실패 시 적절한 에러 처리를 하세요
725
1061
 
1062
+ 4. **환경 변수**
1063
+ - 이 패키지는 환경 변수를 직접 읽지 않습니다
1064
+ - 모든 값은 함수 파라미터로 전달해야 합니다
1065
+ - 하드코딩된 값(서비스 ID, URL 등)이 없도록 주의하세요
1066
+
726
1067
  ## 📝 타입 정의
727
1068
 
728
1069
  ### `JWTPayload`
@@ -789,31 +1130,48 @@ interface ServiceInfo {
789
1130
  }
790
1131
  ```
791
1132
 
792
- ### SSO API 응답 타입
1133
+ ### `MiddlewareConfig`
1134
+
1135
+ ```typescript
1136
+ interface MiddlewareConfig {
1137
+ serviceId: string;
1138
+ publicPaths: string[];
1139
+ subscriptionRequiredPaths: string[];
1140
+ subscriptionExemptApiPaths: string[];
1141
+ authApiPaths: string[];
1142
+ roleAccessConfig: RoleAccessConfig[];
1143
+ rolePaths: Record<string, string>;
1144
+ systemAdminRole: string;
1145
+ errorPath: string;
1146
+ }
1147
+ ```
793
1148
 
794
- #### `SSOUpsertResponse`
1149
+ ### `MiddlewareOptions`
795
1150
 
796
1151
  ```typescript
797
- interface SSOUpsertResponse {
798
- success: boolean;
799
- message?: string;
800
- user?: {
801
- id: string;
802
- email: string;
803
- name: string;
804
- };
1152
+ interface MiddlewareOptions {
1153
+ secret: string;
1154
+ isProduction: boolean;
1155
+ cookieDomain?: string;
1156
+ getNextAuthToken?: (req: NextRequest) => Promise<JWT | null>;
1157
+ ssoBaseURL: string;
1158
+ authServiceKey?: string;
805
1159
  }
806
1160
  ```
807
1161
 
808
- #### `SSOErrorResponse`
1162
+ ### `RoleAccessConfig`
809
1163
 
810
1164
  ```typescript
811
- interface SSOErrorResponse {
812
- error: string;
1165
+ interface RoleAccessConfig {
1166
+ paths: string[];
1167
+ role: string;
1168
+ allowedRoles?: string[];
813
1169
  message?: string;
814
1170
  }
815
1171
  ```
816
1172
 
1173
+ ### SSO API 응답 타입
1174
+
817
1175
  #### `SSORefreshTokenResponse`
818
1176
 
819
1177
  ```typescript
@@ -840,44 +1198,6 @@ interface SSOGetRefreshTokenResponse {
840
1198
  }
841
1199
  ```
842
1200
 
843
- #### `SSORegisterResponse`
844
-
845
- ```typescript
846
- interface SSORegisterResponse {
847
- success: boolean;
848
- message?: string;
849
- user?: {
850
- id: string;
851
- email: string;
852
- name: string;
853
- };
854
- }
855
- ```
856
-
857
- ### 사용 예시
858
-
859
- ```typescript
860
- import type {
861
- SSOUpsertResponse,
862
- SSOErrorResponse,
863
- SSORefreshTokenResponse,
864
- SSOGetRefreshTokenResponse,
865
- SSORegisterResponse,
866
- } from "@thinkingcat/auth-utils";
867
-
868
- // SSO API 호출 시 타입 사용
869
- const response = await fetch("/api/sso/refresh", {
870
- method: "POST",
871
- body: JSON.stringify({ refreshToken }),
872
- });
873
-
874
- const result = (await response.json()) as SSORefreshTokenResponse;
875
- if (result.success && result.accessToken) {
876
- // 타입 안전하게 사용
877
- console.log(result.accessToken);
878
- }
879
- ```
880
-
881
1201
  ### `ResponseLike`
882
1202
 
883
1203
  ```typescript
@@ -936,10 +1256,35 @@ npm install
936
1256
  - 브라우저 개발자 도구에서 쿠키 설정 확인
937
1257
  - 도메인 및 경로 설정 확인
938
1258
 
1259
+ ### 문제 5: "ssoBaseURL is required" 에러
1260
+
1261
+ **해결 방법:**
1262
+ 모든 SSO 관련 함수는 `ssoBaseURL`을 필수 파라미터로 받습니다. 환경 변수에서 값을 가져와서 전달하세요:
1263
+
1264
+ ```typescript
1265
+ const ssoBaseURL = process.env.SSO_BASE_URL!;
1266
+ if (!ssoBaseURL) {
1267
+ throw new Error("SSO_BASE_URL environment variable is required");
1268
+ }
1269
+ ```
1270
+
1271
+ ### 문제 6: "cookiePrefix is required" 에러
1272
+
1273
+ **해결 방법:**
1274
+ `setCustomTokens`와 `clearAuthCookies` 함수는 `cookiePrefix`를 필수 파라미터로 받습니다. 서비스 ID를 사용하거나 명시적으로 전달하세요:
1275
+
1276
+ ```typescript
1277
+ const cookiePrefix = "myservice"; // 서비스별로 변경
1278
+ setCustomTokens(response, accessToken, {
1279
+ cookiePrefix, // 필수
1280
+ isProduction: true,
1281
+ });
1282
+ ```
1283
+
939
1284
  ## 📦 패키지 정보
940
1285
 
941
1286
  - **패키지명**: `@thinkingcat/auth-utils`
942
- - **버전**: `1.0.4`
1287
+ - **버전**: `1.0.8`
943
1288
  - **라이선스**: MIT
944
1289
  - **저장소**: npm registry
945
1290