@fluojs/jwt 1.0.0 → 1.0.2

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
@@ -59,6 +59,8 @@ JWT 설정이 다른 provider에서 와야 한다면, `JwtModule.forRootAsync(..
59
59
 
60
60
  비동기 등록도 동기 경로와 동일한 JWT provider surface를 export하며, 여기에는 `RefreshTokenService`가 포함됩니다. 단, 이 서비스를 실제로 resolve하려면 `refreshToken` 옵션이 구성되어 있어야 합니다.
61
61
 
62
+ `forRootAsync(...)`는 `inject`에 나열된 provider에서 module-level `JwtVerifierOptions` 객체 하나를 resolve합니다. 이 factory는 요청별 상태를 받지 않습니다. 테넌트별 secret이나 identity provider가 필요한 경우 tenant lookup은 애플리케이션의 auth layer에 두고, token verification 중에는 `kid` 같은 token metadata와 미리 구성한 `keys[]`, `jwksUri`, 또는 `secretOrKeyProvider`를 사용해 검증 material을 선택하세요.
63
+
62
64
  ```typescript
63
65
  import { Module, type Token } from '@fluojs/core';
64
66
  import { JwtModule } from '@fluojs/jwt';
@@ -148,10 +150,14 @@ const verifier = new DefaultJwtVerifier({
148
150
 
149
151
  `jwksRequestTimeoutMs`의 기본값은 `5_000`이며, 예산을 넘기면 진행 중인 JWKS fetch를 abort합니다.
150
152
 
153
+ JWKS key는 `jwksCacheTtl` 밀리초 동안 cache되며 기본값은 `600_000`입니다. in-memory cache는 `jwksCacheMaxEntries`로 제한되고 기본값은 `100`입니다. lookup 전 만료된 entry를 정리하고, 제한을 넘으면 가장 오래 보관된 key를 제거합니다. 수동 shutdown이나 identity-provider 재설정 시에는 `JwksClient.dispose()` / `DefaultJwtVerifier.dispose()`로 보관 중인 remote key material을 비울 수 있습니다. `jwksCacheTtl`을 `0`으로 설정하면 bounded fetch timeout은 유지하면서 key 보관만 비활성화합니다.
154
+
151
155
  `JwtService.verify(token, options)`는 호출 단위의 알고리즘/클레임 정책 재정의(`issuer`, `audience`, `clockSkewSeconds`, `maxAge`, `requireExp`)를 적용하더라도, 내부 JWKS client나 정적 key-resolution cache를 다시 만들지 않습니다. 호출 단위 검증은 `jwksUri`, `keys[]`, `publicKey`, `secret`, `secretOrKeyProvider` 같은 구성된 key source 자체를 교체하지는 않습니다.
152
156
 
153
157
  호환되는 키가 여러 개 설정되어 있으면 `kid`가 검증 키를 구분합니다. 호환되는 정적 키가 하나뿐이면 `kid` 없이도 토큰을 검증할 수 있고, JWKS 기반 검증은 원격 key set과 cache policy를 따릅니다.
154
158
 
159
+ 멀티테넌트 시스템에서는 발행된 토큰 header에 tenant-specific `kid`를 넣고 호환되는 key source를 미리 구성하는 방식을 권장합니다. `secretOrKeyProvider`는 decoded token header만 인자로 받으므로, request header, route param, 기타 request-context tenant hint는 JWT verifier 호출 전에 애플리케이션 수준 strategy/guard 코드에서 처리해야 합니다.
160
+
155
161
  ### 리프레시 토큰
156
162
 
157
163
  `RefreshTokenService`는 전용 HMAC refresh-token 경로를 사용합니다. `refreshToken.secret`은 access-token 서명 키와 별도로 설정하세요. Rotation은 `RefreshTokenStore.rotate(...)`를 사용해 현재 토큰을 소비 처리하고 대체 토큰을 같은 durable store 작업 안에서 저장할 수 있습니다. 따라서 성공한 rotation은 저장된 후속 토큰 없이 기존 토큰만 소비하지 않습니다. 기존 atomic `consume(...)` hook만 구현한 store도 계속 지원하지만, 대체 토큰 저장의 내구성은 store가 소유한 `rotate(...)` 작업에 달려 있습니다.
@@ -162,7 +168,9 @@ JWT 서명과 검증에는 `algorithms`에 지원되는 알고리즘이 하나
162
168
 
163
169
  액세스 토큰 TTL도 양의 유한 숫자여야 합니다. `accessTokenTtlSeconds`를 생략하면 `DefaultJwtSigner`는 문서화된 기본값인 `3600`초를 사용합니다. 소수 초는 JWT NumericDate `exp` 클레임에 그대로 보존됩니다. `0`, 음수 또는 유한하지 않은 값이 제공되면 토큰을 발행하기 전에 `JwtConfigurationError`로 실패합니다.
164
170
 
165
- 검증은 잘못된 시간 정책에 대해 fail closed로 동작합니다. 검증에 참여하는 `exp`, `nbf`, `iat` 클레임은 유한한 JWT NumericDate 숫자여야 하며, `clockSkewSeconds`도 음수가 아닌 유한 숫자여야 합니다. 유한하지 않은 값은 expiration, not-before, age check를 늘리는 대신 거부됩니다.
171
+ 검증은 잘못된 시간 정책에 대해 fail closed로 동작합니다. 검증에 참여하는 `exp`, `nbf`, `iat` 클레임은 유한한 JWT NumericDate 숫자여야 하며, `clockSkewSeconds`도 음수가 아닌 유한 숫자여야 합니다. 유한하지 않은 값은 expiration, not-before, age check를 늘리는 대신 거부됩니다. verifier 시간이 `exp` NumericDate에 도달하면 토큰은 만료된 것으로 처리되며, 양수 clock skew가 경계를 덮지 않는 한 equality도 만료로 간주합니다.
172
+
173
+ 루트 `@fluojs/jwt` import surface는 runtime-specific 인증 경로를 선택하기 전에도 안전하게 로드할 수 있습니다. Node.js `node:crypto` primitive는 서명, 검증, JWKS key parsing, refresh-token id 생성이 실제로 실행될 때만 lazy load됩니다. 이 방식은 기존 public export를 유지하면서 module import 시점의 Node-specific crypto 작업을 피합니다.
166
174
 
167
175
  ## 공개 API 개요
168
176
 
@@ -179,6 +187,7 @@ JWT 서명과 검증에는 `algorithms`에 지원되는 알고리즘이 하나
179
187
  - `JwtVerifierOptions`: 알고리즘, 키, 검증 정책 설정을 위한 타입입니다.
180
188
  - `SignOptions`, `VerifyOptions`: 호출 단위 서명 및 검증 재정의 타입입니다.
181
189
  - `JwtClaims`, `JwtSigner`, `JwtVerifier`, `JwtKeyEntry`, `JwtAlgorithm`: 공개 서명 및 검증 계약입니다.
190
+ - `RefreshTokenOptions`, `RefreshTokenStore`, `RefreshTokenRecord`, `RefreshTokenConsumeInput`, `RefreshTokenRotateInput`, `RefreshTokenConsumeResult`: refresh-token 저장, rotation, replay detection 계약입니다.
182
191
 
183
192
  ### 에러와 diagnostics
184
193
  - `JwtVerificationError`, `JwtInvalidTokenError`, `JwtExpiredTokenError`, `JwtConfigurationError`: 타입이 지정된 JWT 실패입니다.
package/README.md CHANGED
@@ -59,6 +59,8 @@ Use `JwtModule.forRootAsync(...)` when your JWT settings must come from another
59
59
 
60
60
  Async registration exports the same JWT provider surface as the synchronous path, including `RefreshTokenService`; resolving that service still requires `refreshToken` options to be configured.
61
61
 
62
+ `forRootAsync(...)` resolves one module-level `JwtVerifierOptions` object from the providers listed in `inject`. It does not receive per-request state. For tenant-specific secrets or identity providers, keep tenant lookup in your application auth layer and use token metadata such as `kid` with configured `keys[]`, `jwksUri`, or `secretOrKeyProvider` to select verification material during token verification.
63
+
62
64
  ```typescript
63
65
  import { Module, type Token } from '@fluojs/core';
64
66
  import { JwtModule } from '@fluojs/jwt';
@@ -148,10 +150,14 @@ const verifier = new DefaultJwtVerifier({
148
150
 
149
151
  `jwksRequestTimeoutMs` defaults to `5_000` and aborts the outbound JWKS fetch once that budget is exceeded.
150
152
 
153
+ JWKS keys are cached for `jwksCacheTtl` milliseconds (`600_000` by default) and the in-memory cache is bounded by `jwksCacheMaxEntries` (`100` by default). Expired entries are pruned before lookups, the oldest retained key is evicted when the bound is exceeded, and `JwksClient.dispose()` / `DefaultJwtVerifier.dispose()` clears retained remote key material during manual shutdown or identity-provider reconfiguration. A `jwksCacheTtl` of `0` disables key retention while still using bounded fetch timeouts.
154
+
151
155
  `JwtService.verify(token, options)` applies per-call algorithm and claim-policy overrides (`issuer`, `audience`, `clockSkewSeconds`, `maxAge`, `requireExp`) without rebuilding the underlying JWKS client or static key-resolution cache. Per-call verification does not replace configured key sources such as `jwksUri`, `keys[]`, `publicKey`, `secret`, or `secretOrKeyProvider`.
152
156
 
153
157
  When multiple compatible keys are configured, `kid` disambiguates the verification key. A single compatible static key can verify tokens without `kid`; JWKS-backed verification relies on the remote key set and its cache policy.
154
158
 
159
+ For multi-tenant systems, prefer putting a tenant-specific `kid` in issued token headers and configuring compatible key sources up front. `secretOrKeyProvider` is called with the decoded token header only, so request headers, route params, or other request-context tenant hints must be handled by application-level strategy/guard code before calling the JWT verifier.
160
+
155
161
  ### Refresh tokens
156
162
 
157
163
  `RefreshTokenService` uses a dedicated HMAC refresh-token path. Configure `refreshToken.secret` separately from access-token signing keys. Rotation can use `RefreshTokenStore.rotate(...)` to atomically mark the current token as consumed and persist the replacement token in the same durable store operation, so a successful rotation never consumes the old token without a stored successor. Stores that only implement the older atomic `consume(...)` hook remain supported, but durable replacement persistence depends on the store-owned `rotate(...)` operation.
@@ -162,7 +168,9 @@ JWT signing and verification require at least one supported algorithm in `algori
162
168
 
163
169
  Access-token TTL must also be a positive finite number. When `accessTokenTtlSeconds` is omitted, `DefaultJwtSigner` uses the documented `3600` second default. Fractional seconds are preserved in the JWT NumericDate `exp` claim; when the option is provided as `0`, a negative number, or a non-finite value, signing fails with `JwtConfigurationError` before a token is issued.
164
170
 
165
- Verification fails closed on malformed time policy. `exp`, `nbf`, and `iat` claims that participate in verification must be finite JWT NumericDate numbers, and `clockSkewSeconds` must be a non-negative finite number. Non-finite values are rejected instead of extending expiration, not-before, or age checks.
171
+ Verification fails closed on malformed time policy. `exp`, `nbf`, and `iat` claims that participate in verification must be finite JWT NumericDate numbers, and `clockSkewSeconds` must be a non-negative finite number. Non-finite values are rejected instead of extending expiration, not-before, or age checks. A token is expired when verifier time reaches its `exp` NumericDate; equality is treated as expired unless positive clock skew still covers the boundary.
172
+
173
+ The root `@fluojs/jwt` import surface is safe to load before selecting a runtime-specific authentication path: Node.js `node:crypto` primitives are loaded lazily only when signing, verification, JWKS key parsing, or refresh-token id generation actually executes. This preserves the existing public exports while avoiding Node-specific crypto work at module import time.
166
174
 
167
175
  ## Public API Overview
168
176
 
@@ -179,6 +187,7 @@ Verification fails closed on malformed time policy. `exp`, `nbf`, and `iat` clai
179
187
  - `JwtVerifierOptions`: Configuration for algorithms, keys, and validation policy.
180
188
  - `SignOptions` and `VerifyOptions`: Per-call signing and verification overrides.
181
189
  - `JwtClaims`, `JwtSigner`, `JwtVerifier`, `JwtKeyEntry`, `JwtAlgorithm`: Public signing and verification contracts.
190
+ - `RefreshTokenOptions`, `RefreshTokenStore`, `RefreshTokenRecord`, `RefreshTokenConsumeInput`, `RefreshTokenRotateInput`, and `RefreshTokenConsumeResult`: Refresh-token storage, rotation, and replay-detection contracts.
182
191
 
183
192
  ### Errors and diagnostics
184
193
  - `JwtVerificationError`, `JwtInvalidTokenError`, `JwtExpiredTokenError`, `JwtConfigurationError`: Typed JWT failures.
@@ -1 +1 @@
1
- {"version":3,"file":"refresh-token.d.ts","sourceRoot":"","sources":["../../src/refresh/refresh-token.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAAC;IAC/D,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,OAAO,CAAC,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;IAC9E,MAAM,CAAC,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;CAC7E;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,IAAI,CAAC;CACX;AAED;;GAEG;AACH,MAAM,WAAW,uBAAwB,SAAQ,wBAAwB;IACvE,WAAW,EAAE,kBAAkB,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,MAAM,yBAAyB,GAAG,UAAU,GAAG,cAAc,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,CAAC;AAEvH;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,IAAI,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,iBAAiB,CAAC;CAC1B;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,mBAAmB,GAAG,SAAS,GAAG,mBAAmB,CA6B1G;AAQD;;GAEG;AACH,qBAAa,mBAAmB;IAK5B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAL3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;gBAG5C,OAAO,EAAE,mBAAmB,EACX,MAAM,EAAE,gBAAgB,EACxB,QAAQ,EAAE,kBAAkB;IAKzC,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAMnD,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;IA0EhG,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlD,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAI3C,2BAA2B;YAQ3B,mBAAmB;YAUnB,4BAA4B;YA8B5B,mBAAmB;CA4BlC"}
1
+ {"version":3,"file":"refresh-token.d.ts","sourceRoot":"","sources":["../../src/refresh/refresh-token.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAAC;IAC/D,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,OAAO,CAAC,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;IAC9E,MAAM,CAAC,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;CAC7E;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,IAAI,CAAC;CACX;AAED;;GAEG;AACH,MAAM,WAAW,uBAAwB,SAAQ,wBAAwB;IACvE,WAAW,EAAE,kBAAkB,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,MAAM,yBAAyB,GAAG,UAAU,GAAG,cAAc,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,CAAC;AAEvH;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,IAAI,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,IAAI,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,iBAAiB,CAAC;CAC1B;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,mBAAmB,GAAG,SAAS,GAAG,mBAAmB,CA6B1G;AAQD;;GAEG;AACH,qBAAa,mBAAmB;IAK5B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAL3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;gBAG5C,OAAO,EAAE,mBAAmB,EACX,MAAM,EAAE,gBAAgB,EACxB,QAAQ,EAAE,kBAAkB;IAKzC,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAOnD,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;IA0EhG,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlD,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAI3C,2BAA2B;YAQ3B,mBAAmB;YAUnB,4BAA4B;YA+B5B,mBAAmB;CA4BlC"}
@@ -1,4 +1,3 @@
1
- import { randomUUID } from 'node:crypto';
2
1
  import { JwtConfigurationError, JwtExpiredTokenError, JwtInvalidTokenError } from '../errors.js';
3
2
 
4
3
  /**
@@ -62,6 +61,9 @@ export class RefreshTokenService {
62
61
  this.options = normalizeRefreshTokenOptions(options);
63
62
  }
64
63
  async issueRefreshToken(subject) {
64
+ const {
65
+ randomUUID
66
+ } = await import('node:crypto');
65
67
  const family = randomUUID();
66
68
  return this.issueRefreshTokenWithFamily(subject, family);
67
69
  }
@@ -152,6 +154,9 @@ export class RefreshTokenService {
152
154
  }
153
155
  async createRefreshTokenWithFamily(subject, family) {
154
156
  const now = Math.floor(Date.now() / 1000);
157
+ const {
158
+ randomUUID
159
+ } = await import('node:crypto');
155
160
  const tokenId = randomUUID();
156
161
  const expiresAt = new Date((now + this.options.expiresInSeconds) * 1000);
157
162
  const record = {
@@ -1,4 +1,4 @@
1
- import { type KeyObject } from 'node:crypto';
1
+ import type { KeyObject } from 'node:crypto';
2
2
  /**
3
3
  * Represents the jwks client.
4
4
  */
@@ -6,10 +6,21 @@ export declare class JwksClient {
6
6
  private readonly uri;
7
7
  private readonly cacheTtl;
8
8
  private readonly requestTimeoutMs;
9
+ private readonly cacheMaxEntries;
9
10
  private readonly cache;
10
- constructor(uri: string, cacheTtl?: number, requestTimeoutMs?: number);
11
+ private lifecycleGeneration;
12
+ constructor(uri: string, cacheTtl?: number, requestTimeoutMs?: number, cacheMaxEntries?: number);
13
+ /**
14
+ * Clears all cached JWKS key material held by this client.
15
+ *
16
+ * Call this during application shutdown when the verifier/client lifecycle is
17
+ * owned manually, or when rotating identity-provider configuration.
18
+ */
19
+ dispose(): void;
11
20
  private isAbortError;
12
21
  getSigningKey(kid: string): Promise<KeyObject>;
22
+ private pruneExpiredCacheEntries;
23
+ private evictOldestCacheEntries;
13
24
  private fetchKeys;
14
25
  }
15
26
  //# sourceMappingURL=jwks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"jwks.d.ts","sourceRoot":"","sources":["../../src/signing/jwks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAa9D;;GAEG;AACH,qBAAa,UAAU;IAInB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IALnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA4D;gBAG/D,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,MAAgB,EAC1B,gBAAgB,GAAE,MAAc;IAGnD,OAAO,CAAC,YAAY;IAId,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;YA+BtC,SAAS;CAqCxB"}
1
+ {"version":3,"file":"jwks.d.ts","sourceRoot":"","sources":["../../src/signing/jwks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAiC7C;;GAEG;AACH,qBAAa,UAAU;IAKnB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,eAAe;IAPlC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA4D;IAClF,OAAO,CAAC,mBAAmB,CAAK;gBAGb,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,MAAgB,EAC1B,gBAAgB,GAAE,MAAc,EAChC,eAAe,GAAE,MAAuC;IAO3E;;;;;OAKG;IACH,OAAO,IAAI,IAAI;IAKf,OAAO,CAAC,YAAY;IAId,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IA2CpD,OAAO,CAAC,wBAAwB;IAQhC,OAAO,CAAC,uBAAuB;YAYjB,SAAS;CAsCxB"}
@@ -1,31 +1,71 @@
1
- import { createPublicKey } from 'node:crypto';
2
1
  import { JwtConfigurationError, JwtInvalidTokenError } from '../errors.js';
2
+ const DEFAULT_JWKS_CACHE_MAX_ENTRIES = 100;
3
+ function assertNonNegativeFiniteNumber(value, label) {
4
+ if (!Number.isFinite(value) || value < 0) {
5
+ throw new JwtConfigurationError(`${label} must be a non-negative finite number.`);
6
+ }
7
+ }
8
+ function assertPositiveFiniteNumber(value, label) {
9
+ if (!Number.isFinite(value) || value <= 0) {
10
+ throw new JwtConfigurationError(`${label} must be a positive finite number.`);
11
+ }
12
+ }
13
+ function assertPositiveInteger(value, label) {
14
+ if (!Number.isInteger(value) || value <= 0) {
15
+ throw new JwtConfigurationError(`${label} must be a positive integer.`);
16
+ }
17
+ }
18
+
3
19
  /**
4
20
  * Represents the jwks client.
5
21
  */
6
22
  export class JwksClient {
7
23
  cache = new Map();
8
- constructor(uri, cacheTtl = 600_000, requestTimeoutMs = 5_000) {
24
+ lifecycleGeneration = 0;
25
+ constructor(uri, cacheTtl = 600_000, requestTimeoutMs = 5_000, cacheMaxEntries = DEFAULT_JWKS_CACHE_MAX_ENTRIES) {
9
26
  this.uri = uri;
10
27
  this.cacheTtl = cacheTtl;
11
28
  this.requestTimeoutMs = requestTimeoutMs;
29
+ this.cacheMaxEntries = cacheMaxEntries;
30
+ assertNonNegativeFiniteNumber(cacheTtl, 'JWKS cache ttl');
31
+ assertPositiveFiniteNumber(requestTimeoutMs, 'JWKS request timeout');
32
+ assertPositiveInteger(cacheMaxEntries, 'JWKS cache max entries');
33
+ }
34
+
35
+ /**
36
+ * Clears all cached JWKS key material held by this client.
37
+ *
38
+ * Call this during application shutdown when the verifier/client lifecycle is
39
+ * owned manually, or when rotating identity-provider configuration.
40
+ */
41
+ dispose() {
42
+ this.lifecycleGeneration += 1;
43
+ this.cache.clear();
12
44
  }
13
45
  isAbortError(error) {
14
46
  return error instanceof Error && error.name === 'AbortError';
15
47
  }
16
48
  async getSigningKey(kid) {
17
49
  const now = Date.now();
50
+ this.pruneExpiredCacheEntries(now);
18
51
  const cached = this.cache.get(kid);
19
52
  if (cached && cached.expiresAt > now) {
20
53
  return cached.key;
21
54
  }
55
+ const fetchGeneration = this.lifecycleGeneration;
22
56
  const keys = await this.fetchKeys();
57
+ if (fetchGeneration !== this.lifecycleGeneration) {
58
+ throw new JwtConfigurationError('JWKS client was disposed while fetching keys.');
59
+ }
23
60
  const jwk = keys.find(entry => entry.kid === kid);
24
61
  if (!jwk) {
25
62
  throw new JwtInvalidTokenError('JWT key id was not found in JWKS.');
26
63
  }
27
64
  let key;
28
65
  try {
66
+ const {
67
+ createPublicKey
68
+ } = await import('node:crypto');
29
69
  key = createPublicKey({
30
70
  format: 'jwk',
31
71
  key: jwk
@@ -33,18 +73,38 @@ export class JwksClient {
33
73
  } catch {
34
74
  throw new JwtConfigurationError('Unable to parse JWKS key into a public key.');
35
75
  }
36
- this.cache.set(kid, {
37
- expiresAt: now + this.cacheTtl,
38
- key
39
- });
76
+ if (this.cacheTtl > 0 && fetchGeneration === this.lifecycleGeneration) {
77
+ this.cache.set(kid, {
78
+ expiresAt: now + this.cacheTtl,
79
+ key
80
+ });
81
+ this.evictOldestCacheEntries();
82
+ }
40
83
  return key;
41
84
  }
85
+ pruneExpiredCacheEntries(now) {
86
+ for (const [kid, cached] of this.cache) {
87
+ if (cached.expiresAt <= now) {
88
+ this.cache.delete(kid);
89
+ }
90
+ }
91
+ }
92
+ evictOldestCacheEntries() {
93
+ while (this.cache.size > this.cacheMaxEntries) {
94
+ const oldestKid = this.cache.keys().next().value;
95
+ if (oldestKid === undefined) {
96
+ return;
97
+ }
98
+ this.cache.delete(oldestKid);
99
+ }
100
+ }
42
101
  async fetchKeys() {
43
102
  let response;
44
103
  const controller = new AbortController();
45
104
  const timeout = setTimeout(() => {
46
105
  controller.abort();
47
106
  }, this.requestTimeoutMs);
107
+ timeout.unref?.();
48
108
  try {
49
109
  response = await fetch(this.uri, {
50
110
  signal: controller.signal
@@ -1 +1 @@
1
- {"version":3,"file":"signer.d.ts","sourceRoot":"","sources":["../../src/signing/signer.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAgB,SAAS,EAAe,kBAAkB,EAAE,MAAM,aAAa,CAAC;AA0D5F;;GAEG;AACH,qBACa,gBAAgB;IAGf,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAiB;gBAEtB,OAAO,EAAE,kBAAkB;IAOlD,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC;IAInD,gBAAgB,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC;IAK1D,OAAO,CAAC,4BAA4B;YAYtB,SAAS;CAkFxB"}
1
+ {"version":3,"file":"signer.d.ts","sourceRoot":"","sources":["../../src/signing/signer.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAgB,SAAS,EAAe,kBAAkB,EAAE,MAAM,aAAa,CAAC;AA0D5F;;GAEG;AACH,qBACa,gBAAgB;IAGf,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAiB;gBAEtB,OAAO,EAAE,kBAAkB;IAOlD,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC;IAInD,gBAAgB,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC;IAK1D,OAAO,CAAC,4BAA4B;YAYtB,SAAS;CAoFxB"}
@@ -4,7 +4,6 @@ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol"
4
4
  function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
5
5
  function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
6
6
  function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
7
- import { createHmac, createSign } from 'node:crypto';
8
7
  import { Inject } from '@fluojs/core';
9
8
  import { JwtConfigurationError } from '../errors.js';
10
9
  import { normalizeRefreshTokenOptions } from '../refresh/refresh-token.js';
@@ -121,6 +120,9 @@ class DefaultJwtSigner {
121
120
  if (!hash) {
122
121
  throw new JwtConfigurationError(`No hash mapping for asymmetric algorithm "${algorithm}".`);
123
122
  }
123
+ const {
124
+ createSign
125
+ } = await import('node:crypto');
124
126
  const signer = createSign(hash);
125
127
  signer.update(signingInput);
126
128
  const isEc = algorithm.startsWith('ES');
@@ -137,6 +139,9 @@ class DefaultJwtSigner {
137
139
  if (!hash) {
138
140
  throw new JwtConfigurationError(`No hash mapping for HMAC algorithm "${algorithm}".`);
139
141
  }
142
+ const {
143
+ createHmac
144
+ } = await import('node:crypto');
140
145
  signatureSegment = encodeBase64Url(createHmac(hash, secret).update(signingInput).digest());
141
146
  }
142
147
  return `${headerSegment}.${payloadSegment}.${signatureSegment}`;
@@ -1,5 +1,5 @@
1
1
  import type { JwtPrincipal, JwtVerifierOptions } from '../types.js';
2
- import { DefaultJwtVerifier } from './verifier.js';
2
+ import type { DefaultJwtVerifier } from './verifier.js';
3
3
  type AccessTokenVerificationOverrides = Pick<JwtVerifierOptions, 'algorithms' | 'audience' | 'clockSkewSeconds' | 'issuer' | 'maxAge' | 'requireExp'>;
4
4
  /**
5
5
  * Applies supported per-call access-token overrides through the verifier's public API.
@@ -1 +1 @@
1
- {"version":3,"file":"verifier-internal.d.ts","sourceRoot":"","sources":["../../src/signing/verifier-internal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAEnD,KAAK,gCAAgC,GAAG,IAAI,CAC1C,kBAAkB,EAClB,YAAY,GAAG,UAAU,GAAG,kBAAkB,GAAG,QAAQ,GAAG,QAAQ,GAAG,YAAY,CACpF,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,kBAAkB,EAC5B,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,OAAO,CAAC,gCAAgC,CAAC,GACnD,OAAO,CAAC,YAAY,CAAC,CAEvB"}
1
+ {"version":3,"file":"verifier-internal.d.ts","sourceRoot":"","sources":["../../src/signing/verifier-internal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEpE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAExD,KAAK,gCAAgC,GAAG,IAAI,CAC1C,kBAAkB,EAClB,YAAY,GAAG,UAAU,GAAG,kBAAkB,GAAG,QAAQ,GAAG,QAAQ,GAAG,YAAY,CACpF,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,kBAAkB,EAC5B,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,OAAO,CAAC,gCAAgC,CAAC,GACnD,OAAO,CAAC,YAAY,CAAC,CAEvB"}
@@ -23,6 +23,14 @@ export declare class DefaultJwtVerifier {
23
23
  private readonly refreshVerificationOptions;
24
24
  constructor(options: JwtVerifierOptions);
25
25
  verifyAccessToken(token: string): Promise<JwtPrincipal>;
26
+ /**
27
+ * Releases verifier-owned remote JWKS cache entries.
28
+ *
29
+ * Call this when disposing a manually managed verifier or replacing its remote
30
+ * identity-provider configuration. Static keys and provider callbacks are not
31
+ * owned by the verifier and are therefore left untouched.
32
+ */
33
+ dispose(): void;
26
34
  /**
27
35
  * Verifies a JWT access token with per-call claim-policy overrides while reusing configured key sources.
28
36
  *
@@ -1 +1 @@
1
- {"version":3,"file":"verifier.d.ts","sourceRoot":"","sources":["../../src/signing/verifier.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAA0B,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAG1G;;GAEG;AACH,eAAO,MAAM,WAAW,eAAiC,CAAC;AAE1D;;GAEG;AACH,eAAO,MAAM,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAI3D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAOjE,CAAC;AAmDF,KAAK,gCAAgC,GAAG,IAAI,CAC1C,kBAAkB,EAClB,YAAY,GAAG,UAAU,GAAG,kBAAkB,GAAG,QAAQ,GAAG,QAAQ,GAAG,YAAY,CACpF,CAAC;AA+LF;;GAEG;AACH,qBACa,kBAAkB;IAMjB,OAAO,CAAC,QAAQ,CAAC,OAAO;IALpC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyB;IACpD,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAqB;IACxD,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAqB;IAC/D,OAAO,CAAC,QAAQ,CAAC,0BAA0B,CAAiC;gBAE/C,OAAO,EAAE,kBAAkB;IAalD,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAI7D;;;;;;;;;;OAUG;IACG,8BAA8B,CAClC,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,OAAO,CAAC,gCAAgC,CAAC,GACnD,OAAO,CAAC,YAAY,CAAC;IAqBlB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAQ9D,OAAO,CAAC,gCAAgC;YAsB1B,WAAW;IA+BzB,OAAO,CAAC,kBAAkB;YAUZ,oBAAoB;YAgBpB,wBAAwB;YAsBxB,8BAA8B;YAsB9B,kBAAkB;IAWhC,OAAO,CAAC,mBAAmB;IAwB3B,OAAO,CAAC,oBAAoB;IA2B5B,OAAO,CAAC,yBAAyB;YAiBnB,oBAAoB;CAOnC"}
1
+ {"version":3,"file":"verifier.d.ts","sourceRoot":"","sources":["../../src/signing/verifier.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAA0B,YAAY,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAG1G;;GAEG;AACH,eAAO,MAAM,WAAW,eAAiC,CAAC;AAE1D;;GAEG;AACH,eAAO,MAAM,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAI3D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAOjE,CAAC;AAmDF,KAAK,gCAAgC,GAAG,IAAI,CAC1C,kBAAkB,EAClB,YAAY,GAAG,UAAU,GAAG,kBAAkB,GAAG,QAAQ,GAAG,QAAQ,GAAG,YAAY,CACpF,CAAC;AAiMF;;GAEG;AACH,qBACa,kBAAkB;IAMjB,OAAO,CAAC,QAAQ,CAAC,OAAO;IALpC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyB;IACpD,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAqB;IACxD,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAqB;IAC/D,OAAO,CAAC,QAAQ,CAAC,0BAA0B,CAAiC;gBAE/C,OAAO,EAAE,kBAAkB;IAalD,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAI7D;;;;;;OAMG;IACH,OAAO,IAAI,IAAI;IAIf;;;;;;;;;;OAUG;IACG,8BAA8B,CAClC,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,OAAO,CAAC,gCAAgC,CAAC,GACnD,OAAO,CAAC,YAAY,CAAC;IAqBlB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAQ9D,OAAO,CAAC,gCAAgC;YAsB1B,WAAW;IA+BzB,OAAO,CAAC,kBAAkB;YAUZ,oBAAoB;YAgBpB,wBAAwB;YAsBxB,8BAA8B;YAsB9B,kBAAkB;IAWhC,OAAO,CAAC,mBAAmB;IAwB3B,OAAO,CAAC,oBAAoB;IA2B5B,OAAO,CAAC,yBAAyB;YAiBnB,oBAAoB;CAOnC"}
@@ -4,7 +4,6 @@ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol"
4
4
  function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
5
5
  function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
6
6
  function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
7
- import { createHmac, createVerify, timingSafeEqual } from 'node:crypto';
8
7
  import { Inject } from '@fluojs/core';
9
8
  import { JwtConfigurationError, JwtExpiredTokenError, JwtInvalidTokenError } from '../errors.js';
10
9
  import { normalizeRefreshTokenOptions } from '../refresh/refresh-token.js';
@@ -130,11 +129,15 @@ function resolveStaticPublicKey(options, keyState, kid) {
130
129
  }
131
130
  return keyState.defaultPublicKey ?? options.publicKey;
132
131
  }
133
- function verifyHmacSignature(algorithm, secret, signingInput, signatureSegment) {
132
+ async function verifyHmacSignature(algorithm, secret, signingInput, signatureSegment) {
134
133
  const hash = HMAC_HASH[algorithm];
135
134
  if (!hash) {
136
135
  throw new JwtInvalidTokenError();
137
136
  }
137
+ const {
138
+ createHmac,
139
+ timingSafeEqual
140
+ } = await import('node:crypto');
138
141
  const expected = encodeBase64Url(createHmac(hash, secret).update(signingInput).digest());
139
142
  const expectedBuf = Buffer.from(expected, 'base64url');
140
143
  const actualBuf = Buffer.from(signatureSegment, 'base64url');
@@ -142,11 +145,14 @@ function verifyHmacSignature(algorithm, secret, signingInput, signatureSegment)
142
145
  throw new JwtInvalidTokenError();
143
146
  }
144
147
  }
145
- function verifyAsymmetricSignature(algorithm, publicKey, signingInput, signatureSegment) {
148
+ async function verifyAsymmetricSignature(algorithm, publicKey, signingInput, signatureSegment) {
146
149
  const hash = ASYMMETRIC_HASH[algorithm];
147
150
  if (!hash) {
148
151
  throw new JwtInvalidTokenError();
149
152
  }
153
+ const {
154
+ createVerify
155
+ } = await import('node:crypto');
150
156
  const verifier = createVerify(hash);
151
157
  verifier.update(signingInput);
152
158
  const isEc = algorithm.startsWith('ES');
@@ -206,7 +212,7 @@ class DefaultJwtVerifier {
206
212
  constructor(options) {
207
213
  this.options = options;
208
214
  assertJwtAlgorithms(options.algorithms, 'JWT verifier');
209
- this.jwksClient = options.jwksUri ? new JwksClient(options.jwksUri, options.jwksCacheTtl, options.jwksRequestTimeoutMs) : undefined;
215
+ this.jwksClient = options.jwksUri ? new JwksClient(options.jwksUri, options.jwksCacheTtl, options.jwksRequestTimeoutMs, options.jwksCacheMaxEntries) : undefined;
210
216
  this.keyResolutionState = createKeyResolutionState(options.keys);
211
217
  this.refreshVerificationOptions = options.refreshToken ? this.createRefreshVerificationOptions(normalizeRefreshTokenOptions(options.refreshToken)) : undefined;
212
218
  this.refreshKeyResolutionState = createKeyResolutionState(this.refreshVerificationOptions?.keys);
@@ -215,6 +221,17 @@ class DefaultJwtVerifier {
215
221
  return this.verifyToken(token, this.options, this.keyResolutionState, this.jwksClient);
216
222
  }
217
223
 
224
+ /**
225
+ * Releases verifier-owned remote JWKS cache entries.
226
+ *
227
+ * Call this when disposing a manually managed verifier or replacing its remote
228
+ * identity-provider configuration. Static keys and provider callbacks are not
229
+ * owned by the verifier and are therefore left untouched.
230
+ */
231
+ dispose() {
232
+ this.jwksClient?.dispose();
233
+ }
234
+
218
235
  /**
219
236
  * Verifies a JWT access token with per-call claim-policy overrides while reusing configured key sources.
220
237
  *
@@ -299,7 +316,7 @@ class DefaultJwtVerifier {
299
316
  if (!secret) {
300
317
  throw new JwtConfigurationError('JWT secret is not configured.');
301
318
  }
302
- verifyHmacSignature(header.alg, secret, signingInput, signatureSegment);
319
+ await verifyHmacSignature(header.alg, secret, signingInput, signatureSegment);
303
320
  }
304
321
  async verifyAsymmetricTokenSignature(header, signingInput, signatureSegment, options, keyResolutionState, jwksClient) {
305
322
  const providerKey = await this.resolveProviderKey(options, header);
@@ -307,7 +324,7 @@ class DefaultJwtVerifier {
307
324
  if (!publicKey) {
308
325
  throw new JwtConfigurationError('JWT public key is not configured.');
309
326
  }
310
- verifyAsymmetricSignature(header.alg, publicKey, signingInput, signatureSegment);
327
+ await verifyAsymmetricSignature(header.alg, publicKey, signingInput, signatureSegment);
311
328
  }
312
329
  async resolveProviderKey(options, header) {
313
330
  if (!options.secretOrKeyProvider) {
@@ -326,7 +343,7 @@ class DefaultJwtVerifier {
326
343
  assertFiniteNumericDateClaim(payload, 'exp');
327
344
  assertFiniteNumericDateClaim(payload, 'nbf');
328
345
  this.validateMaxAgeClaims(payload, options.maxAge, clockSkew, now);
329
- if (typeof payload.exp === 'number' && payload.exp + clockSkew < now) {
346
+ if (typeof payload.exp === 'number' && payload.exp + clockSkew <= now) {
330
347
  throw new JwtExpiredTokenError();
331
348
  }
332
349
  if (typeof payload.nbf === 'number' && payload.nbf - clockSkew > now) {
package/dist/types.d.ts CHANGED
@@ -25,6 +25,8 @@ export interface JwtVerifierOptions {
25
25
  global?: boolean;
26
26
  issuer?: string;
27
27
  jwksCacheTtl?: number;
28
+ /** Maximum number of remote JWKS public keys retained in the in-memory cache. Defaults to `100`. */
29
+ jwksCacheMaxEntries?: number;
28
30
  jwksRequestTimeoutMs?: number;
29
31
  jwksUri?: string;
30
32
  keys?: JwtKeyEntry[];
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEtE;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAEnH;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,YAAY,EAAE,CAAC;IAC3B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6EAA6E;IAC7E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,WAAW,EAAE,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,mBAAmB,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACrH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,YAAY,CAAC,EAAE,mBAAmB,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,SAAU,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACxD,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;CACzD;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACrD"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "signing",
10
10
  "verification"
11
11
  ],
12
- "version": "1.0.0",
12
+ "version": "1.0.2",
13
13
  "private": false,
14
14
  "license": "MIT",
15
15
  "repository": {
@@ -36,9 +36,9 @@
36
36
  "dist"
37
37
  ],
38
38
  "dependencies": {
39
- "@fluojs/core": "^1.0.0",
40
- "@fluojs/di": "^1.0.0",
41
- "@fluojs/runtime": "^1.0.0"
39
+ "@fluojs/core": "^1.0.3",
40
+ "@fluojs/di": "^1.1.0",
41
+ "@fluojs/runtime": "^1.1.7"
42
42
  },
43
43
  "devDependencies": {
44
44
  "vitest": "^3.2.4"