@fluojs/passport 1.0.4 → 2.0.0

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.ko.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  <p><a href="./README.md"><kbd>English</kbd></a> <strong><kbd>한국어</kbd></strong></p>
4
4
 
5
+ Node.js 지원 범위는 `>=24.0.0 <27`입니다. 업그레이드 절차는 [Node.js 지원 및 마이그레이션](../../docs/reference/node-support.ko.md)을 참조하세요.
6
+
5
7
  fluo를 위한 인증 실행 계층으로, 어떤 `AuthStrategy`든 공통 `AuthGuard`를 통해 요청 컨텍스트(`requestContext.principal`)에 연결합니다.
6
8
 
7
9
  ## 목차
@@ -31,29 +33,17 @@ npm install @fluojs/passport
31
33
 
32
34
  ### 1. 모듈 등록
33
35
 
34
- 사용할 전략을 정의하고 `PassportModule.forRoot(...)`를 통해 등록합니다.
36
+ 내장 bearer JWT preset을 사용하고 `PassportModule.forRoot(...)`로 등록합니다.
35
37
 
36
38
  ```typescript
37
- import { Inject, Module } from '@fluojs/core';
38
- import type { GuardContext } from '@fluojs/http';
39
- import { DefaultJwtVerifier, JwtModule } from '@fluojs/jwt';
40
- import { AuthenticationRequiredError, PassportModule, type AuthStrategy } from '@fluojs/passport';
41
-
42
- @Inject(DefaultJwtVerifier)
43
- export class BearerJwtStrategy implements AuthStrategy {
44
- constructor(private readonly verifier: DefaultJwtVerifier) {}
45
-
46
- async authenticate(context: GuardContext) {
47
- const authorization = context.requestContext.request.headers.authorization;
48
- const [scheme, token] = typeof authorization === 'string' ? authorization.split(' ') : [];
49
-
50
- if (scheme !== 'Bearer' || !token) {
51
- throw new AuthenticationRequiredError('Bearer access token is required.');
52
- }
53
-
54
- return this.verifier.verifyAccessToken(token);
55
- }
56
- }
39
+ import { Module } from '@fluojs/core';
40
+ import { JwtModule } from '@fluojs/jwt';
41
+ import {
42
+ BEARER_JWT_STRATEGY_NAME,
43
+ BearerJwtStrategy,
44
+ createBearerJwtStrategyRegistration,
45
+ PassportModule,
46
+ } from '@fluojs/passport';
57
47
 
58
48
  @Module({
59
49
  imports: [
@@ -64,8 +54,8 @@ export class BearerJwtStrategy implements AuthStrategy {
64
54
  secret: 'your-secure-secret',
65
55
  }),
66
56
  PassportModule.forRoot(
67
- { defaultStrategy: 'jwt' },
68
- [{ name: 'jwt', token: BearerJwtStrategy }],
57
+ { defaultStrategy: BEARER_JWT_STRATEGY_NAME },
58
+ [createBearerJwtStrategyRegistration()],
69
59
  ),
70
60
  ],
71
61
  providers: [BearerJwtStrategy],
@@ -73,11 +63,11 @@ export class BearerJwtStrategy implements AuthStrategy {
73
63
  export class AuthModule {}
74
64
  ```
75
65
 
76
- JWT 기반 passport 전략에는 두 가지 module wiring이 모두 필요합니다. `JwtModule.forRoot(...)`는 `DefaultJwtVerifier`를 등록하고, `PassportModule.forRoot(...)`는 `@UseAuth('jwt')`가 resolve할 named strategy를 등록합니다. `DefaultJwtVerifier.verifyAccessToken(...)` 결과를 반환하면 `AuthGuard`가 `requestContext.principal`에 기록하는 정규화 principal 계약(`subject`, `claims`, `issuer`, `audience`, `roles`, `scopes`)이 보존됩니다.
66
+ `BearerJwtStrategy`는 `Authorization: Bearer <token>` credential을 위한 first-party strategy preset입니다. JWT 기반 passport 전략에는 두 가지 module wiring이 모두 필요합니다. `JwtModule.forRoot(...)`는 `DefaultJwtVerifier`를 등록하고, `providers` 항목은 `BearerJwtStrategy`를 DI를 통해 해당 verifier에 연결하며, `PassportModule.forRoot(...)`는 `@UseAuth('jwt')`가 resolve할 named strategy를 등록합니다. `DefaultJwtVerifier.verifyAccessToken(...)` 결과를 변경 없이 반환하면 `AuthGuard`가 `requestContext.principal`에 기록하는 정규화 principal 계약(`subject`, `claims`, `issuer`, `audience`, `roles`, `scopes`)이 보존됩니다.
77
67
 
78
68
  ### 2. 라우트 보호
79
69
 
80
- `@UseAuth()`와 `@RequireScopes()`를 사용하여 인증을 강제합니다.
70
+ `@UseAuth()`로 인증을, `@RequireScopes()`로 인가를 강제합니다.
81
71
 
82
72
  ```typescript
83
73
  import { Controller, Get, type RequestContext } from '@fluojs/http';
@@ -96,6 +86,46 @@ export class ProfileController {
96
86
 
97
87
  ## 일반적인 패턴
98
88
 
89
+ ### Bearer JWT 프리셋
90
+
91
+ 애플리케이션이 `Authorization: Bearer <token>` 요청을 인증한다면 내장 `BearerJwtStrategy`를 사용하세요. 애플리케이션이 직접 parsing 코드를 소유할 필요가 없습니다.
92
+
93
+ ```typescript
94
+ import { Module } from '@fluojs/core';
95
+ import { JwtModule } from '@fluojs/jwt';
96
+ import {
97
+ BEARER_JWT_STRATEGY_NAME,
98
+ BearerJwtStrategy,
99
+ createBearerJwtStrategyRegistration,
100
+ PassportModule,
101
+ } from '@fluojs/passport';
102
+
103
+ @Module({
104
+ imports: [
105
+ JwtModule.forRoot({
106
+ algorithms: ['HS256'],
107
+ audience: 'my-app',
108
+ issuer: 'my-api',
109
+ secret: 'your-secure-secret',
110
+ }),
111
+ PassportModule.forRoot(
112
+ { defaultStrategy: BEARER_JWT_STRATEGY_NAME },
113
+ [createBearerJwtStrategyRegistration()],
114
+ ),
115
+ ],
116
+ providers: [BearerJwtStrategy],
117
+ })
118
+ export class AuthModule {}
119
+ ```
120
+
121
+ `BearerJwtStrategy`는 `PassportModule`과 `JwtModule`을 import하는 같은 애플리케이션 모듈에 provider로 등록하세요. 검증 설정의 소유자는 계속 `JwtModule`이며, preset은 결합된 JWT/Passport module facade를 도입하지 않습니다. Preset은 안정적인 전략 이름 `BEARER_JWT_STRATEGY_NAME`(`'jwt'`)으로 등록되므로 보호된 라우트는 계속 `@UseAuth('jwt')`를 사용하고, `createBearerJwtStrategyRegistration()`이 대응하는 `AuthStrategyRegistration`을 반환합니다.
122
+
123
+ Credential extraction은 엄격합니다. Credential은 RFC 6750 `b64token`을 따라 ASCII 문자, 숫자, `-`, `.`, `_`, `~`, `+`, `/`가 하나 이상 있고 뒤에만 optional `=` padding이 올 수 있습니다. `?`, 내부 `=`, Unicode, whitespace, control character는 검증 전에 거부합니다. `Authorization` header가 없거나 비어 있으면 `AuthenticationRequiredError`가 발생하고, adapter가 array-valued `Authorization` header를 전달하면 첫 번째 entry만 읽습니다. 잘못된 scheme이거나 malformed인 header는 `AuthenticationFailedError`를 발생시키며, scheme 비교는 대소문자를 구분하지 않고 ASCII space를 하나 이상 사용합니다. 누락되거나 malformed인 credential은 bare `WWW-Authenticate: Bearer` challenge를 직렬화합니다. 검증 실패는 `DefaultJwtVerifier`의 error causality를 유지합니다. 만료된 credential은 `AuthenticationExpiredError`, `JwtInvalidTokenError` credential은 `AuthenticationFailedError`를 발생시키며, 둘 다 원본 JWT error를 `cause`로 보존하고 `WWW-Authenticate: Bearer error="invalid_token"`을 직렬화합니다. `JwtConfigurationError`와 verifier-provider infrastructure failure는 credential failure로 바꾸지 않고 변경 없이 전파합니다. `AuthGuard`는 credential failure만 canonical `401 Unauthorized` response로 변환하고, scope 불일치는 여전히 `403 Forbidden`을 만듭니다.
124
+
125
+ `BearerJwtStrategy`는 `DefaultJwtVerifier.verifyAccessToken(...)`이 반환한 정규화 `JwtPrincipal`을 변경 없이 반환하여 `subject`, `claims`, `issuer`, `audience`, `roles`, `scopes`를 보존합니다.
126
+
127
+ 커스텀 `AuthStrategy`는 token revocation, 계정 상태 확인, 대체 extraction, 애플리케이션 소유 정책을 위한 extension point로 남아 있습니다.
128
+
99
129
  ### Passport.js 브릿지 (Bridge)
100
130
 
101
131
  표준 Passport.js 전략(예: `passport-google-oauth20`)을 fluo의 DI와 비동기 수명 주기에 맞춰 쉽게 변환하여 사용할 수 있습니다.
@@ -106,7 +136,27 @@ const googleBridge = createPassportJsStrategyBridge('google', GoogleStrategy, {
106
136
  });
107
137
  ```
108
138
 
109
- 브릿지는 각 Passport.js 전략 실행을 정확히 한 번만 정착(settle)시킵니다. 전략은 바인딩된 Passport 액션(`success`, `fail`, `redirect`, `pass`, `error`) 하나를 호출해야 하며, promise rejection, 액션 없이 완료된 promise, 그리고 제한된 action timeout을 초과한 callback-style 실행은 요청을 미해결 상태로 두지 않고 인증 실패로 처리됩니다. 커스텀 `mapPrincipal` 함수는 비어 있지 않은 `subject`와 객체 형태의 `claims`를 포함한 유효한 fluo `Principal`을 반환해야 합니다.
139
+ `createPassportJsStrategyBridge(...)`는 의도적으로 문서화된 manual-composition compatibility helper입니다. helper는 `PassportModule.forRoot(...)`가 소비하는 provider bundle과 대응하는 `AuthStrategyRegistration`을 반환합니다. 애플리케이션은 `PassportModule`을 import하는 같은 module에 bridge provider를 등록하고, `googleBridge.strategy`를 strategy registry에 전달해야 합니다.
140
+
141
+ ```typescript
142
+ @Module({
143
+ imports: [
144
+ PassportModule.forRoot({ defaultStrategy: 'google' }, [googleBridge.strategy]),
145
+ ],
146
+ providers: [GoogleStrategy, ...googleBridge.providers],
147
+ })
148
+ export class AuthModule {}
149
+ ```
150
+
151
+ 이 bridge helper는 third-party Passport.js strategy instance를 `AuthGuard`가 실행하기 전에 provider로 바인딩해야 하므로 Passport.js adapter에 대해 공식적으로 허용되는 module-facade 예외입니다. 애플리케이션-facing 인증 표면은 계속 `PassportModule`, `@UseAuth(...)`, `AuthGuard`이며, 이 helper가 이를 대체하지 않습니다.
152
+
153
+ 브릿지는 각 Passport.js 전략 실행을 정확히 한 번만 정착(settle)시킵니다. 전략은 바인딩된 Passport 액션(`success`, `fail`, `redirect`, `pass`, `error`) 중 하나를 호출해야 하며, promise rejection, 액션 없이 완료된 promise, 그리고 제한된 action timeout을 초과한 callback-style 실행은 요청을 미해결 상태로 두지 않고 인증 실패로 처리됩니다. 브릿지는 `authenticate()`의 반환 값을 소비하지 않으며, 바인딩된 액션만이 요청을 정착시킵니다. `handled: true`를 포함한 모든 `AuthStrategyResult`는 전략이 response를 commit한 뒤에는 `principal`을 함께 포함하더라도 완전히 terminal입니다. 이때 `AuthGuard`는 principal validation, scope check, `requestContext.principal` 할당, protected handler 실행을 모두 건너뜁니다. 커스텀 `mapPrincipal` 함수는 비어 있지 않은 `subject`와 객체 형태의 `claims`를 포함한 유효한 fluo `Principal`을 반환해야 합니다.
154
+
155
+ 브릿지는 활성 platform adapter가 raw host request를 제공하면 그 값을, 그렇지 않으면 정규화된 fluo request를 `authenticate(request, options)`에 전달합니다. Passport-initialized host request를 생성하지는 않으므로, Passport middleware augmentation(`request.logIn`, `request.user`, session state)이나 adapter별 request field에 의존하는 strategy는 cutover 전에 [NestJS → fluo Migration Map](../../docs/getting-started/migrate-from-nestjs.ko.md#passportjs-bridge-migration)의 compatibility checklist를 확인해야 합니다.
156
+
157
+ `actionTimeoutMs` 기본값은 `30_000`밀리초이며 0 이상인 유한한 숫자여야 합니다. `0`으로 설정하면 다음 timer turn에 timeout settlement를 예약합니다. 음수, `NaN`, 무한대 값은 settlement 제한을 비활성화하지 않고 bridge strategy 생성 시 `RangeError`를 발생시킵니다.
158
+
159
+ 애플리케이션 종료가 시작되면 진행 중인 모든 bridge 실행을 취소하고 action timeout을 정리한 뒤, 설정된 timeout까지 request state를 유지하지 않고 pending authentication을 reject합니다. Bridge는 일반 application lifecycle에 참여하므로, application 또는 application context가 close될 때 이 취소가 실행됩니다.
110
160
 
111
161
  ### 쿠키 인증 프리셋
112
162
 
@@ -127,6 +177,7 @@ import {
127
177
  CookieAuthModule.forRoot(),
128
178
  JwtModule.forRoot({
129
179
  algorithms: ['HS256'],
180
+ global: true,
130
181
  secret: 'your-secure-secret',
131
182
  }),
132
183
  PassportModule.forRoot(
@@ -138,13 +189,19 @@ import {
138
189
  export class AuthModule {}
139
190
  ```
140
191
 
141
- 애플리케이션 모듈에서 cookie-auth 지원이 필요하면 `CookieAuthModule.forRoot(...)`, `JwtModule.forRoot(...)`, `PassportModule.forRoot(...)`를 함께 import 하세요. Cookie preset은 `CookieAuthStrategy`와 cookie option을 제공하고, JWT 검증은 여전히 `@fluojs/jwt`에서 오며, passport registry는 여전히 `PassportModule.forRoot(...)`에서 옵니다.
192
+ 애플리케이션 모듈에서 cookie-auth 지원이 필요하면 `CookieAuthModule.forRoot(...)`, `JwtModule.forRoot(...)`, `PassportModule.forRoot(...)`를 함께 import 하세요. 이 graph에서 `CookieAuthModule`과 `JwtModule`은 sibling import이므로, cookie module이 `CookieAuthStrategy`를 resolve할 때 `DefaultJwtVerifier`를 볼 수 있도록 문서화된 JWT option `global: true`를 설정해야 합니다. Cookie preset은 `CookieAuthStrategy`와 cookie option을 제공하고, JWT 검증은 여전히 `@fluojs/jwt`에서 오며, passport registry는 여전히 `PassportModule.forRoot(...)`에서 옵니다.
193
+
194
+ `CookieAuthModule.forRoot(...)`는 애플리케이션 등록을 위한 canonical module-first entrypoint입니다. `createCookieAuthPreset(...)`은 provider graph를 직접 조립하는 host를 위한 compatibility bundle로 공개되어 있으며, 동일한 cookie-auth provider와 대응하는 strategy registration을 반환합니다. 애플리케이션 문서, generated code, 일반 app module에서는 module facade를 우선 사용하세요.
142
195
 
143
196
  `CookieAuthStrategy`는 `@fluojs/jwt`가 정규화한 JWT principal 계약을 보존하며, `subject`, `claims`, `issuer`, `audience`, `roles`, `scopes`를 그대로 전달합니다.
144
197
 
145
198
  Cookie access token은 비어 있지 않은 문자열이어야 합니다. `requireAccessToken: false`일 때만 누락된 cookie가 `{ authenticated: false }`로 resolve될 수 있으며, 존재하지만 malformed인 cookie 값은 JWT 검증 전에 항상 인증 실패로 처리됩니다.
146
199
 
147
- `CookieManager`는 underlying adapter가 기존 header를 `set-cookie`처럼 다른 casing으로 저장했더라도, response에 이미 설정된 cookie를 덮어쓰지 않고 access-token refresh-token `Set-Cookie` 값을 append합니다.
200
+ Cookie 검증 실패는 문서화된 분류를 유지합니다. 만료된 access token은 `AuthenticationExpiredError`, 유효하지 않은 access token은 `AuthenticationFailedError`, 누락되거나 malformed인 access-token cookie는 `AuthenticationRequiredError`를 발생시킵니다. 원본 `@fluojs/jwt` error는 `cause`로 보존되며, `AuthGuard`는 세 경우 모두 HTTP `401`로 응답합니다.
201
+
202
+ `CookieManagerConfig.cookieOptions`는 `SetCookieOptions`를 받습니다. `accessTokenTtlSeconds`와 `refreshTokenTtlSeconds` field는 positional TTL 인자가 생략되었을 때 해당 token cookie의 기본 `Max-Age`가 되며, 명시적인 positional TTL이 항상 우선합니다.
203
+
204
+ `CookieManager`는 underlying adapter가 기존 header를 `set-cookie`처럼 다른 casing으로 저장했더라도, response에 이미 설정된 cookie를 덮어쓰지 않고 access-token 및 refresh-token `Set-Cookie` 값을 append합니다. portable HTTP serializer를 사용하므로 cookie 값은 emit 전에 percent-encoding되며, malformed cookie name 또는 attribute는 invalid header를 emit하는 대신 validation에서 실패합니다.
148
205
 
149
206
  보호된 라우트는 계속 `@UseAuth(...)`를 사용해야 합니다. `requireAccessToken: false`를 설정해도 쿠키가 없을 때는 익명 principal이 아니라 명시적인 미인증 결과를 반환하므로, 보호된 라우트는 요청을 계속 거부합니다.
150
207
 
@@ -171,6 +228,7 @@ export class SessionController {
171
228
  ```typescript
172
229
  import { Module } from '@fluojs/core';
173
230
  import { Controller, Post, type RequestContext } from '@fluojs/http';
231
+ import { JwtModule } from '@fluojs/jwt';
174
232
  import {
175
233
  PassportModule,
176
234
  REFRESH_TOKEN_STRATEGY_NAME,
@@ -179,29 +237,36 @@ import {
179
237
  UseAuth,
180
238
  } from '@fluojs/passport';
181
239
 
240
+ @Controller('/auth')
241
+ export class AuthController {
242
+ @Post('/refresh')
243
+ @UseAuth('refresh-token')
244
+ async refresh(input: never, ctx: RequestContext) {
245
+ return ctx.principal; // 새 토큰 쌍이 포함된 principal 반환
246
+ }
247
+ }
248
+
182
249
  @Module({
250
+ controllers: [AuthController],
183
251
  imports: [
252
+ JwtModule.forRoot({
253
+ algorithms: ['HS256'],
254
+ global: true,
255
+ secret: 'your-access-token-secret',
256
+ }),
184
257
  RefreshTokenModule.forRoot(MyRefreshTokenService),
185
258
  PassportModule.forRoot(
186
259
  { defaultStrategy: REFRESH_TOKEN_STRATEGY_NAME },
187
260
  [{ name: REFRESH_TOKEN_STRATEGY_NAME, token: RefreshTokenStrategy }],
188
261
  ),
189
262
  ],
190
- providers: [MyRefreshTokenService],
191
263
  })
192
264
  export class AuthModule {}
193
-
194
- @Controller('/auth')
195
- export class AuthController {
196
- @Post('/refresh')
197
- @UseAuth('refresh-token')
198
- async refresh(input: never, ctx: RequestContext) {
199
- return ctx.principal; // 새 토큰 쌍이 포함된 principal 반환
200
- }
201
- }
202
265
  ```
203
266
 
204
- `RefreshTokenModule.forRoot(...)`를 `PassportModule.forRoot(...)`와 함께 import 하여 refresh-token 전략과 공유 `REFRESH_TOKEN_SERVICE` alias를 같은 모듈 wiring에서 사용하세요.
267
+ `JwtModule.forRoot(...)`, `RefreshTokenModule.forRoot(...)`, `PassportModule.forRoot(...)`를 함께 import 하세요. 이 graph에서 `RefreshTokenStrategy`는 `JwtModule`의 sibling인 `RefreshTokenModule`에 속하므로, 이 예제는 문서화된 `global: true` option을 설정해 refresh module이 strategy를 resolve할 때 `DefaultJwtVerifier`를 볼 수 있게 합니다. `RefreshTokenModule.forRoot(MyRefreshTokenService)`는 service class를 refresh module 내부에 등록하고 공유 `REFRESH_TOKEN_SERVICE` alias로 export합니다. 이 class에 constructor dependency가 있다면 application-owned module에서 dependency제공하고 export한 뒤 `imports`로 전달하세요. String 및 symbol service token은 `RefreshTokenModule`에서 보여야 합니다. Imported module 또는 global module에서 export하거나 bootstrap runtime provider로 등록하세요. `MyRefreshTokenService`를 application module의 `providers`에 다시 등록하지 마세요. 중복 provider registration이 되며 bootstrap은 기본적으로 warning을 남기고 `duplicateProviderPolicy: 'throw'`에서는 거부할 수 있습니다. Application code가 service를 사용할 때는 export된 `REFRESH_TOKEN_SERVICE` alias를 inject하고, refresh route가 실제로 존재하려면 application module이 `AuthController`를 등록해야 합니다. `PassportModule`은 `@UseAuth('refresh-token')`가 resolve하는 named strategy를 등록합니다.
268
+
269
+ 교환에 성공하면 `ctx.principal`은 `RefreshTokenPrincipal` shape으로 resolve됩니다. Rotation된 token 쌍은 `claims.accessToken`과 `claims.refreshToken`에 중첩되고, 검증된 `subject`는 최상위에 위치합니다. 별도로 export되는 `RefreshTokenAuthResult` 타입은 refresh endpoint가 client에 반환하는 application-facing 교환 payload를 설명합니다.
205
270
 
206
271
  `RefreshTokenStrategy`는 `body.refreshToken`, `Authorization: Bearer ...`, `x-refresh-token`에서 token을 읽습니다. Malformed non-string token은 인증 실패로 처리됩니다. Rotation 후에는 `@fluojs/jwt`가 반환한 정규화 access-token principal subject를 신뢰합니다. `JwtRefreshTokenAdapter`는 `secret`과 backing store가 필요하며, `store: 'memory'`는 development 및 single-instance deployment용이고 rotation은 store consume contract를 통해 재사용을 감지합니다.
207
272
 
@@ -233,23 +298,27 @@ Identity-link 결정을 모델링하려면 `createConservativeAccountLinkPolicy(
233
298
  - `defineAuthRequirement(...)`, `getOwnAuthRequirement(...)`, `getAuthRequirement(...)`: Custom decorator나 tooling을 `AuthGuard`와 통합할 때 auth requirement metadata를 읽고 쓰는 공개 helper입니다.
234
299
  - Scope requirement는 일반적으로 `@RequireScopes(...)`로 작성합니다. 더 낮은 수준의 scope normalization helper는 internal로 남아 있으며 package root export의 일부가 아닙니다.
235
300
 
301
+ ### Bearer JWT preset
302
+ - `BearerJwtStrategy`, `BEARER_JWT_STRATEGY_NAME`: 내장 bearer credential strategy와 안정적인 등록 이름(`'jwt'`)입니다.
303
+ - Bearer helper: `createBearerJwtStrategyRegistration`.
304
+
236
305
  ### Cookie auth preset
237
306
  - `CookieAuthModule`: 내장 cookie-auth preset의 모듈 진입점입니다.
238
307
  - `CookieAuthStrategy`, `COOKIE_AUTH_STRATEGY_NAME`, `COOKIE_AUTH_OPTIONS`, `DEFAULT_COOKIE_AUTH_OPTIONS`, `DEFAULT_COOKIE_OPTIONS`: Cookie strategy wiring token, preset 기본값, response-cookie 기본값입니다.
239
- - `CookieAuthOptions`, `CookieAuthPresetConfig`, `CookieManagerConfig`, `CookieOptions`, `SetCookieOptions`: Cookie strategy 및 response cookie 설정 타입입니다.
308
+ - `CookieAuthOptions`, `CookieAuthPresetConfig`, `CookieManagerConfig`, `CookieOptions`, `SetCookieOptions`: Cookie strategy 및 response cookie 설정 타입입니다. `CookieManagerConfig.cookieOptions`는 `SetCookieOptions`를 받으며, token별 TTL field는 기본 cookie `Max-Age`가 됩니다.
240
309
  - `CookieManager`: HttpOnly access/refresh token cookie를 설정하고 제거하는 유틸리티입니다.
241
- - Cookie helper: `createCookieAuthPreset`, `createCookieAuthStrategyRegistration`, `createCookieManager`, `normalizeCookieAuthOptions`.
310
+ - Cookie helper: `createCookieAuthPreset`(compatibility-only manual provider bundle), `createCookieAuthStrategyRegistration`(low-level registration helper), `createCookieManager`, `normalizeCookieAuthOptions`.
242
311
 
243
312
  ### Refresh token preset
244
313
  - `RefreshTokenModule`: 내장 refresh-token preset의 모듈 진입점입니다.
245
314
  - `RefreshTokenStrategy`, `REFRESH_TOKEN_STRATEGY_NAME`, `REFRESH_TOKEN_SERVICE`: Refresh-token strategy 및 service alias wiring입니다.
246
- - `RefreshTokenService`, `RefreshTokenInput`, `RefreshTokenAuthResult`: Application service contract exchange payload shape입니다.
315
+ - `RefreshTokenService`, `RefreshTokenInput`, `RefreshTokenAuthResult`, `RefreshTokenPrincipal`: Application service contract, exchange payload shape, 그리고 교환 성공 후 `ctx.principal`에 resolve되는 principal shape입니다.
247
316
  - `JwtRefreshTokenAdapter`: `@fluojs/jwt`의 refresh logic을 passport interface로 연결합니다.
248
317
  - `REFRESH_TOKEN_MODULE_OPTIONS`, `RefreshTokenModuleOptions`: 필수 `secret`과 `store` 계약을 포함하는 JWT 기반 refresh-token adapter 설정 token 및 option입니다.
249
318
  - Refresh helper: `createRefreshTokenStrategyRegistration`.
250
319
 
251
320
  ### Passport.js bridge
252
- - `createPassportJsStrategyBridge(...)`: Passport.js strategy를 fluo `AuthStrategy`로 변환합니다.
321
+ - `createPassportJsStrategyBridge(...)`: Passport.js strategy를 fluo `AuthStrategy`로 변환하고 `PassportModule.forRoot(...)`용 provider와 대응하는 strategy registration을 반환하는 compatibility helper입니다.
253
322
  - `PassportJsAuthStrategy`, `PassportJsStrategyLike`, `PassportJsPrincipalMapperInput`, `PassportJsPrincipalMapper`, `PassportJsAuthStrategyOptions`, `PassportJsStrategyBridge`: Bridge strategy, mapper, 설정, provider bundle 계약입니다.
254
323
 
255
324
  ### Account linking
@@ -263,7 +332,7 @@ Identity-link 결정을 모델링하려면 `createConservativeAccountLinkPolicy(
263
332
  - `createPassportPlatformDiagnosticIssues(...)`: Empty registry, 누락된 default strategy, cookie preset readiness, refresh-token backing store readiness 문제에 대한 diagnostic issue를 생성합니다.
264
333
  - `PassportPlatformStatusSnapshot`, `PassportStatusAdapterInput`: Status helper input/output 계약입니다.
265
334
 
266
- `UseOptionalAuth`는 scope가 필요 없는 route에서만 credential 누락을 우회합니다. Scoped route에는 여전히 principal이 필요합니다. Passport.js bridge의 `redirect()`는 response를 commit하고 protected handler를 건너뛰며, `pass()`와 Passport action 없이 완료된 strategy는 인증 실패입니다. Refresh-token backing store status 및 diagnostic surface는 readiness, health, details, diagnostic cause를 노출하기 전에 secret처럼 보이는 reason 문자열을 redact합니다.
335
+ `UseOptionalAuth`는 scope가 필요 없는 route에서만 credential 누락을 우회합니다. Scoped route에는 여전히 principal이 필요합니다. `handled: true`를 포함한 `AuthHandledResult`는 strategy가 response를 commit한 뒤에만 terminal이며, `principal`을 함께 포함한 결과도 여기에 포함됩니다. Passport.js bridge의 `redirect()`는 response를 commit하고 protected handler를 건너뛰며, `pass()`와 Passport action 없이 완료된 strategy는 인증 실패입니다. Refresh-token backing store status 및 diagnostic surface는 readiness, health, details, diagnostic cause를 노출하기 전에 secret처럼 보이는 reason 문자열을 redact합니다.
267
336
 
268
337
  ## 관련 패키지
269
338
 
@@ -274,4 +343,4 @@ Identity-link 결정을 모델링하려면 `createConservativeAccountLinkPolicy(
274
343
 
275
344
  - `packages/passport/src/guard.test.ts`: 가드 실행 및 권한 강제 패턴 예제.
276
345
  - `packages/passport/src/adapters/passport-js.ts`: Passport.js 브릿지 구현체.
277
- - `examples/auth-jwt-passport/src/auth/bearer.strategy.ts`: 표준 JWT 전략 구현 예제.
346
+ - `packages/passport/src/bearer/bearer-jwt.ts`: 내장 bearer JWT strategy preset.
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  <p><strong><kbd>English</kbd></strong> <a href="./README.ko.md"><kbd>한국어</kbd></a></p>
4
4
 
5
+ Node.js support is `>=24.0.0 <27`. See [Node.js support and migration](../../docs/reference/node-support.md) before upgrading.
6
+
5
7
  Strategy-agnostic auth execution layer for fluo. It routes any `AuthStrategy` through a generic `AuthGuard` into the request context, populating `requestContext.principal`.
6
8
 
7
9
  ## Table of Contents
@@ -31,29 +33,17 @@ npm install @fluojs/passport
31
33
 
32
34
  ### 1. Register Modules
33
35
 
34
- Define your strategies and register them using `PassportModule.forRoot(...)`.
36
+ Use the built-in bearer JWT preset and register it with `PassportModule.forRoot(...)`.
35
37
 
36
38
  ```typescript
37
- import { Inject, Module } from '@fluojs/core';
38
- import type { GuardContext } from '@fluojs/http';
39
- import { DefaultJwtVerifier, JwtModule } from '@fluojs/jwt';
40
- import { AuthenticationRequiredError, PassportModule, type AuthStrategy } from '@fluojs/passport';
41
-
42
- @Inject(DefaultJwtVerifier)
43
- export class BearerJwtStrategy implements AuthStrategy {
44
- constructor(private readonly verifier: DefaultJwtVerifier) {}
45
-
46
- async authenticate(context: GuardContext) {
47
- const authorization = context.requestContext.request.headers.authorization;
48
- const [scheme, token] = typeof authorization === 'string' ? authorization.split(' ') : [];
49
-
50
- if (scheme !== 'Bearer' || !token) {
51
- throw new AuthenticationRequiredError('Bearer access token is required.');
52
- }
53
-
54
- return this.verifier.verifyAccessToken(token);
55
- }
56
- }
39
+ import { Module } from '@fluojs/core';
40
+ import { JwtModule } from '@fluojs/jwt';
41
+ import {
42
+ BEARER_JWT_STRATEGY_NAME,
43
+ BearerJwtStrategy,
44
+ createBearerJwtStrategyRegistration,
45
+ PassportModule,
46
+ } from '@fluojs/passport';
57
47
 
58
48
  @Module({
59
49
  imports: [
@@ -64,8 +54,8 @@ export class BearerJwtStrategy implements AuthStrategy {
64
54
  secret: 'your-secure-secret',
65
55
  }),
66
56
  PassportModule.forRoot(
67
- { defaultStrategy: 'jwt' },
68
- [{ name: 'jwt', token: BearerJwtStrategy }],
57
+ { defaultStrategy: BEARER_JWT_STRATEGY_NAME },
58
+ [createBearerJwtStrategyRegistration()],
69
59
  ),
70
60
  ],
71
61
  providers: [BearerJwtStrategy],
@@ -73,11 +63,11 @@ export class BearerJwtStrategy implements AuthStrategy {
73
63
  export class AuthModule {}
74
64
  ```
75
65
 
76
- JWT-based passport strategies require both pieces of module wiring: `JwtModule.forRoot(...)` registers `DefaultJwtVerifier`, and `PassportModule.forRoot(...)` registers the named strategy that `@UseAuth('jwt')` resolves. Returning the `DefaultJwtVerifier.verifyAccessToken(...)` result preserves the normalized principal contract (`subject`, `claims`, `issuer`, `audience`, `roles`, and `scopes`) that `AuthGuard` writes to `requestContext.principal`.
66
+ `BearerJwtStrategy` is the first-party strategy preset for `Authorization: Bearer <token>` credentials. JWT-based passport strategies require both pieces of module wiring: `JwtModule.forRoot(...)` registers `DefaultJwtVerifier`, the `providers` entry exposes `BearerJwtStrategy` to that verifier through DI, and `PassportModule.forRoot(...)` registers the named strategy that `@UseAuth('jwt')` resolves. Returning the `DefaultJwtVerifier.verifyAccessToken(...)` result unchanged preserves the normalized principal contract (`subject`, `claims`, `issuer`, `audience`, `roles`, and `scopes`) that `AuthGuard` writes to `requestContext.principal`.
77
67
 
78
68
  ### 2. Protect Routes
79
69
 
80
- Use `@UseAuth()` and `@RequireScopes()` to enforce authentication.
70
+ Use `@UseAuth()` to enforce authentication and `@RequireScopes()` to enforce authorization.
81
71
 
82
72
  ```typescript
83
73
  import { Controller, Get, type RequestContext } from '@fluojs/http';
@@ -96,6 +86,46 @@ export class ProfileController {
96
86
 
97
87
  ## Common Patterns
98
88
 
89
+ ### Bearer JWT Preset
90
+
91
+ Use the built-in `BearerJwtStrategy` when your app authenticates `Authorization: Bearer <token>` requests; no application-owned parsing code is required.
92
+
93
+ ```typescript
94
+ import { Module } from '@fluojs/core';
95
+ import { JwtModule } from '@fluojs/jwt';
96
+ import {
97
+ BEARER_JWT_STRATEGY_NAME,
98
+ BearerJwtStrategy,
99
+ createBearerJwtStrategyRegistration,
100
+ PassportModule,
101
+ } from '@fluojs/passport';
102
+
103
+ @Module({
104
+ imports: [
105
+ JwtModule.forRoot({
106
+ algorithms: ['HS256'],
107
+ audience: 'my-app',
108
+ issuer: 'my-api',
109
+ secret: 'your-secure-secret',
110
+ }),
111
+ PassportModule.forRoot(
112
+ { defaultStrategy: BEARER_JWT_STRATEGY_NAME },
113
+ [createBearerJwtStrategyRegistration()],
114
+ ),
115
+ ],
116
+ providers: [BearerJwtStrategy],
117
+ })
118
+ export class AuthModule {}
119
+ ```
120
+
121
+ Register `BearerJwtStrategy` as a provider in the same application module that imports `PassportModule` and `JwtModule`; `JwtModule` remains the owner of verification configuration, and the preset does not introduce a combined JWT/Passport module facade. The preset registers under the stable strategy name `BEARER_JWT_STRATEGY_NAME` (`'jwt'`), so protected routes keep using `@UseAuth('jwt')`, and `createBearerJwtStrategyRegistration()` returns the matching `AuthStrategyRegistration`.
122
+
123
+ Credential extraction is strict. The credential must match RFC 6750 `b64token`: one or more ASCII letters, digits, `-`, `.`, `_`, `~`, `+`, or `/`, followed only by optional trailing `=` padding. It rejects `?`, internal `=`, Unicode, whitespace, and control characters before verification. A missing or empty `Authorization` header throws `AuthenticationRequiredError`, and when the adapter surfaces an array-valued `Authorization` header only the first entry is read. A wrong-scheme or malformed header throws `AuthenticationFailedError`; the scheme match is case-insensitive and uses one or more ASCII spaces. Missing and malformed credentials serialize the bare `WWW-Authenticate: Bearer` challenge. Verification failures keep `DefaultJwtVerifier` error causality: expired credentials throw `AuthenticationExpiredError`, and `JwtInvalidTokenError` credentials throw `AuthenticationFailedError`, each preserving the original JWT error as `cause` and serializing `WWW-Authenticate: Bearer error="invalid_token"`. `JwtConfigurationError` and verifier-provider infrastructure failures propagate unchanged instead of becoming credential failures. `AuthGuard` maps only credential failures to the canonical `401 Unauthorized` response, and scope mismatches still produce `403 Forbidden`.
124
+
125
+ `BearerJwtStrategy` returns the normalized `JwtPrincipal` from `DefaultJwtVerifier.verifyAccessToken(...)` unchanged, preserving `subject`, `claims`, `issuer`, `audience`, `roles`, and `scopes`.
126
+
127
+ A custom `AuthStrategy` remains the extension point for token revocation, account-state checks, alternate extraction, and application-owned policy.
128
+
99
129
  ### Passport.js Bridge
100
130
 
101
131
  Easily adapt any standard Passport.js strategy (like `passport-google-oauth20`) to work with fluo's DI and async lifecycle.
@@ -106,7 +136,27 @@ const googleBridge = createPassportJsStrategyBridge('google', GoogleStrategy, {
106
136
  });
107
137
  ```
108
138
 
109
- The bridge settles each Passport.js strategy execution exactly once. A strategy must call one of the bound Passport actions (`success`, `fail`, `redirect`, `pass`, or `error`); promise rejections, promise completion without an action, and callback-style executions that exceed the bounded action timeout become authentication failures instead of leaving the request unresolved. Custom `mapPrincipal` functions must return a valid fluo `Principal` with a non-empty `subject` and object `claims`.
139
+ `createPassportJsStrategyBridge(...)` is an intentionally documented manual-composition compatibility helper. It returns the provider bundle and matching `AuthStrategyRegistration` needed by `PassportModule.forRoot(...)`; applications should register the bridge providers in the same module that imports `PassportModule` and pass `googleBridge.strategy` to the strategy registry:
140
+
141
+ ```typescript
142
+ @Module({
143
+ imports: [
144
+ PassportModule.forRoot({ defaultStrategy: 'google' }, [googleBridge.strategy]),
145
+ ],
146
+ providers: [GoogleStrategy, ...googleBridge.providers],
147
+ })
148
+ export class AuthModule {}
149
+ ```
150
+
151
+ This bridge helper is the official exception to the module-facade rule for Passport.js adapters because third-party strategy instances must be bound as providers before `AuthGuard` can execute them. It does not replace `PassportModule`, `@UseAuth(...)`, or `AuthGuard` as the application-facing authentication surface.
152
+
153
+ The bridge settles each Passport.js strategy execution exactly once. A strategy must call one of the bound Passport actions (`success`, `fail`, `redirect`, `pass`, or `error`); promise rejections, promise completion without an action, and callback-style executions that exceed the bounded action timeout become authentication failures instead of leaving the request unresolved. The bridge never consumes an `authenticate()` return value: only a bound action settles the request. Any `AuthStrategyResult` with `handled: true` is fully terminal after the strategy commits a response, even if it also includes a `principal`; `AuthGuard` skips principal validation, scope checks, `requestContext.principal` assignment, and the protected handler. Custom `mapPrincipal` functions must return a valid fluo `Principal` with a non-empty `subject` and object `claims`.
154
+
155
+ The bridge calls `authenticate(request, options)` with the active platform adapter's raw host request when one exists, and with the normalized fluo request otherwise. It never creates a Passport-initialized host request, so strategies that depend on Passport middleware augmentation (`request.logIn`, `request.user`, session state) or on adapter-specific request fields need the compatibility checklist in [NestJS → fluo Migration Map](../../docs/getting-started/migrate-from-nestjs.md#passportjs-bridge-migration) before cutover.
156
+
157
+ `actionTimeoutMs` defaults to `30_000` milliseconds and must be a non-negative finite number. Set it to `0` to schedule timeout settlement on the next timer turn. Negative, `NaN`, and infinite values throw `RangeError` when the bridge strategy is constructed instead of disabling the settlement bound.
158
+
159
+ Application shutdown cancels every in-flight bridge execution, clears its action timeout, and rejects the pending authentication instead of retaining request state through the configured timeout. The bridge participates in the ordinary application lifecycle, so this cancellation runs when the application or application context is closed.
110
160
 
111
161
  ### Cookie Auth Preset
112
162
 
@@ -127,6 +177,7 @@ import {
127
177
  CookieAuthModule.forRoot(),
128
178
  JwtModule.forRoot({
129
179
  algorithms: ['HS256'],
180
+ global: true,
130
181
  secret: 'your-secure-secret',
131
182
  }),
132
183
  PassportModule.forRoot(
@@ -138,13 +189,19 @@ import {
138
189
  export class AuthModule {}
139
190
  ```
140
191
 
141
- Import `CookieAuthModule.forRoot(...)`, `JwtModule.forRoot(...)`, and `PassportModule.forRoot(...)` together when you want cookie-auth support in an application module. The cookie preset provides `CookieAuthStrategy` and cookie options; JWT verification still comes from `@fluojs/jwt`, and the passport registry still comes from `PassportModule.forRoot(...)`.
192
+ Import `CookieAuthModule.forRoot(...)`, `JwtModule.forRoot(...)`, and `PassportModule.forRoot(...)` together when you want cookie-auth support in an application module. `CookieAuthModule` and `JwtModule` are sibling imports in this graph, so set the documented `global: true` JWT option to make `DefaultJwtVerifier` visible when the cookie module resolves `CookieAuthStrategy`. The cookie preset provides `CookieAuthStrategy` and cookie options; JWT verification still comes from `@fluojs/jwt`, and the passport registry still comes from `PassportModule.forRoot(...)`.
193
+
194
+ `CookieAuthModule.forRoot(...)` is the canonical module-first entrypoint for application registration. `createCookieAuthPreset(...)` remains public as a compatibility bundle for manual provider composition; it returns the same cookie-auth providers plus the matching strategy registration for hosts that assemble provider graphs themselves. Prefer the module facade in application docs, generated code, and ordinary app modules.
142
195
 
143
196
  `CookieAuthStrategy` preserves the normalized JWT principal contract from `@fluojs/jwt`, including `subject`, `claims`, `issuer`, `audience`, `roles`, and `scopes`.
144
197
 
145
198
  Cookie access tokens must be non-empty strings. Missing cookies can resolve to `{ authenticated: false }` only when `requireAccessToken: false`; malformed present cookie values always fail authentication before JWT verification.
146
199
 
147
- `CookieManager` appends access-token and refresh-token `Set-Cookie` values without overwriting cookies that were already placed on the response, even when the underlying adapter stores the existing header with different casing such as `set-cookie`.
200
+ Cookie verification failures keep their documented classification: expired access tokens raise `AuthenticationExpiredError`, invalid access tokens raise `AuthenticationFailedError`, and missing or malformed access-token cookies raise `AuthenticationRequiredError`. The originating `@fluojs/jwt` error is preserved as the `cause`, while `AuthGuard` still answers HTTP `401` for every variant.
201
+
202
+ `CookieManagerConfig.cookieOptions` accepts `SetCookieOptions`. Its `accessTokenTtlSeconds` and `refreshTokenTtlSeconds` fields supply the default `Max-Age` for the matching token cookie when the positional TTL argument is omitted; an explicit positional TTL always wins.
203
+
204
+ `CookieManager` appends access-token and refresh-token `Set-Cookie` values without overwriting cookies that were already placed on the response, even when the underlying adapter stores the existing header with different casing such as `set-cookie`. It uses the portable HTTP serializer, so cookie values are percent-encoded before they are emitted and malformed cookie names or attributes fail validation instead of emitting invalid headers.
148
205
 
149
206
  Protected routes must keep using `@UseAuth(...)`. If you configure `requireAccessToken: false`, a missing cookie resolves to an explicit unauthenticated result instead of an anonymous principal, so protected routes still reject the request.
150
207
 
@@ -171,6 +228,7 @@ The package provides a built-in `RefreshTokenStrategy` plus the `RefreshTokenMod
171
228
  ```typescript
172
229
  import { Module } from '@fluojs/core';
173
230
  import { Controller, Post, type RequestContext } from '@fluojs/http';
231
+ import { JwtModule } from '@fluojs/jwt';
174
232
  import {
175
233
  PassportModule,
176
234
  REFRESH_TOKEN_STRATEGY_NAME,
@@ -179,29 +237,36 @@ import {
179
237
  UseAuth,
180
238
  } from '@fluojs/passport';
181
239
 
240
+ @Controller('/auth')
241
+ export class AuthController {
242
+ @Post('/refresh')
243
+ @UseAuth('refresh-token')
244
+ async refresh(input: never, ctx: RequestContext) {
245
+ return ctx.principal; // Contains new token pair
246
+ }
247
+ }
248
+
182
249
  @Module({
250
+ controllers: [AuthController],
183
251
  imports: [
252
+ JwtModule.forRoot({
253
+ algorithms: ['HS256'],
254
+ global: true,
255
+ secret: 'your-access-token-secret',
256
+ }),
184
257
  RefreshTokenModule.forRoot(MyRefreshTokenService),
185
258
  PassportModule.forRoot(
186
259
  { defaultStrategy: REFRESH_TOKEN_STRATEGY_NAME },
187
260
  [{ name: REFRESH_TOKEN_STRATEGY_NAME, token: RefreshTokenStrategy }],
188
261
  ),
189
262
  ],
190
- providers: [MyRefreshTokenService],
191
263
  })
192
264
  export class AuthModule {}
193
-
194
- @Controller('/auth')
195
- export class AuthController {
196
- @Post('/refresh')
197
- @UseAuth('refresh-token')
198
- async refresh(input: never, ctx: RequestContext) {
199
- return ctx.principal; // Contains new token pair
200
- }
201
- }
202
265
  ```
203
266
 
204
- Import `RefreshTokenModule.forRoot(...)` alongside `PassportModule.forRoot(...)` so the refresh-token strategy and shared `REFRESH_TOKEN_SERVICE` alias are available in the same module wiring.
267
+ Import `JwtModule.forRoot(...)`, `RefreshTokenModule.forRoot(...)`, and `PassportModule.forRoot(...)` together. `RefreshTokenStrategy` belongs to `RefreshTokenModule`, which is a sibling of `JwtModule` in this graph, so this example sets the documented `global: true` option to make `DefaultJwtVerifier` visible when the refresh module resolves the strategy. `RefreshTokenModule.forRoot(MyRefreshTokenService)` registers the service class inside the refresh module and exports it through the shared `REFRESH_TOKEN_SERVICE` alias. When that class has constructor dependencies, place those dependencies in an application-owned module that exports them, then pass that module through `imports`. String and symbol service tokens must be visible to `RefreshTokenModule`: export them through an imported module, a global module, or bootstrap runtime providers. Do not also list `MyRefreshTokenService` in the application module's `providers`; that duplicates a provider registration, which bootstrap warns about by default and can reject under `duplicateProviderPolicy: 'throw'`. Inject the exported `REFRESH_TOKEN_SERVICE` alias where application code needs the service, and register `AuthController` in the application module so the refresh route exists. `PassportModule` registers the named strategy resolved by `@UseAuth('refresh-token')`.
268
+
269
+ A successful exchange resolves `ctx.principal` to the `RefreshTokenPrincipal` shape: the rotated pair is nested under `claims.accessToken` and `claims.refreshToken`, with the verified `subject` at the top level. The separate exported `RefreshTokenAuthResult` type describes the application-facing exchange payload a refresh endpoint returns to clients.
205
270
 
206
271
  `RefreshTokenStrategy` reads tokens from `body.refreshToken`, `Authorization: Bearer ...`, or `x-refresh-token`; malformed non-string tokens fail authentication. After rotation, it trusts the normalized access-token principal subject returned by `@fluojs/jwt`. `JwtRefreshTokenAdapter` requires a `secret` and a backing store; `store: 'memory'` is for development and single-instance deployments only, and rotation detects reuse through the store consume contract.
207
272
 
@@ -233,23 +298,27 @@ Use `createConservativeAccountLinkPolicy(...)` and `resolveAccountLinking(...)`
233
298
  - `defineAuthRequirement(...)`, `getOwnAuthRequirement(...)`, `getAuthRequirement(...)`: Public helpers for reading and writing auth requirement metadata when integrating custom decorators or tooling with `AuthGuard`.
234
299
  - Scope requirements are normally authored with `@RequireScopes(...)`; lower-level scope normalization helpers remain internal and are not part of the package root export.
235
300
 
301
+ ### Bearer JWT Preset
302
+ - `BearerJwtStrategy`, `BEARER_JWT_STRATEGY_NAME`: Built-in bearer credential strategy and its stable registration name (`'jwt'`).
303
+ - Bearer helper: `createBearerJwtStrategyRegistration`.
304
+
236
305
  ### Cookie Auth Preset
237
306
  - `CookieAuthModule`: Module entry point for the built-in cookie-auth preset.
238
307
  - `CookieAuthStrategy`, `COOKIE_AUTH_STRATEGY_NAME`, `COOKIE_AUTH_OPTIONS`, `DEFAULT_COOKIE_AUTH_OPTIONS`, `DEFAULT_COOKIE_OPTIONS`: Cookie strategy wiring tokens, preset defaults, and response-cookie defaults.
239
- - `CookieAuthOptions`, `CookieAuthPresetConfig`, `CookieManagerConfig`, `CookieOptions`, `SetCookieOptions`: Cookie strategy and response cookie configuration types.
308
+ - `CookieAuthOptions`, `CookieAuthPresetConfig`, `CookieManagerConfig`, `CookieOptions`, `SetCookieOptions`: Cookie strategy and response cookie configuration types. `CookieManagerConfig.cookieOptions` accepts `SetCookieOptions`, whose per-token TTL fields become default cookie `Max-Age` values.
240
309
  - `CookieManager`: Utility for setting and clearing HttpOnly access/refresh token cookies.
241
- - Cookie helpers: `createCookieAuthPreset`, `createCookieAuthStrategyRegistration`, `createCookieManager`, `normalizeCookieAuthOptions`.
310
+ - Cookie helpers: `createCookieAuthPreset` (compatibility-only manual provider bundle), `createCookieAuthStrategyRegistration` (low-level registration helper), `createCookieManager`, `normalizeCookieAuthOptions`.
242
311
 
243
312
  ### Refresh Token Preset
244
313
  - `RefreshTokenModule`: Module entry point for the built-in refresh-token preset.
245
314
  - `RefreshTokenStrategy`, `REFRESH_TOKEN_STRATEGY_NAME`, `REFRESH_TOKEN_SERVICE`: Refresh-token strategy and service alias wiring.
246
- - `RefreshTokenService`, `RefreshTokenInput`, `RefreshTokenAuthResult`: Application service contract and exchange payload shapes.
315
+ - `RefreshTokenService`, `RefreshTokenInput`, `RefreshTokenAuthResult`, `RefreshTokenPrincipal`: Application service contract, exchange payload shapes, and the principal shape resolved onto `ctx.principal` after a successful exchange.
247
316
  - `JwtRefreshTokenAdapter`: Bridges `@fluojs/jwt` refresh logic to the passport interface.
248
317
  - `REFRESH_TOKEN_MODULE_OPTIONS`, `RefreshTokenModuleOptions`: JWT-backed refresh-token adapter configuration token and options, including the required `secret` and `store` contract.
249
318
  - Refresh helpers: `createRefreshTokenStrategyRegistration`.
250
319
 
251
320
  ### Passport.js Bridge
252
- - `createPassportJsStrategyBridge(...)`: Adapts Passport.js strategies to fluo `AuthStrategy`.
321
+ - `createPassportJsStrategyBridge(...)`: Compatibility helper that adapts Passport.js strategies to fluo `AuthStrategy` and returns providers plus the matching strategy registration for `PassportModule.forRoot(...)`.
253
322
  - `PassportJsAuthStrategy`, `PassportJsStrategyLike`, `PassportJsPrincipalMapperInput`, `PassportJsPrincipalMapper`, `PassportJsAuthStrategyOptions`, `PassportJsStrategyBridge`: Bridge strategy, mapper, configuration, and provider bundle contracts.
254
323
 
255
324
  ### Account Linking
@@ -263,7 +332,7 @@ Use `createConservativeAccountLinkPolicy(...)` and `resolveAccountLinking(...)`
263
332
  - `createPassportPlatformDiagnosticIssues(...)`: Emits diagnostic issues for empty registries, missing default strategies, cookie preset readiness, and refresh-token backing store readiness.
264
333
  - `PassportPlatformStatusSnapshot`, `PassportStatusAdapterInput`: Status helper input/output contracts.
265
334
 
266
- `UseOptionalAuth` only bypasses missing credentials when no scopes are required; scoped routes still need a principal. Passport.js bridge `redirect()` commits the response and skips the protected handler, while `pass()` and strategy completion without a Passport action are authentication failures. Refresh-token backing store status and diagnostic surfaces redact secret-like reason strings before exposing readiness, health, details, or diagnostic causes.
335
+ `UseOptionalAuth` only bypasses missing credentials when no scopes are required; scoped routes still need a principal. `AuthHandledResult` with `handled: true` is terminal only after the strategy commits the response, including results that also carry a `principal`. Passport.js bridge `redirect()` commits the response and skips the protected handler, while `pass()` and strategy completion without a Passport action are authentication failures. Refresh-token backing store status and diagnostic surfaces redact secret-like reason strings before exposing readiness, health, details, or diagnostic causes.
267
336
 
268
337
  ## Related Packages
269
338
 
@@ -274,4 +343,4 @@ Use `createConservativeAccountLinkPolicy(...)` and `resolveAccountLinking(...)`
274
343
 
275
344
  - `packages/passport/src/guard.test.ts`: Guard execution and scope enforcement patterns.
276
345
  - `packages/passport/src/adapters/passport-js.ts`: Implementation of the Passport.js bridge.
277
- - `examples/auth-jwt-passport/src/auth/bearer.strategy.ts`: JWT strategy implementation.
346
+ - `packages/passport/src/bearer/bearer-jwt.ts`: Built-in bearer JWT strategy preset.