@udibo/oauth2 0.29.0 → 0.30.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.
@@ -2,10 +2,11 @@
2
2
  * Token reader that validates JWT access tokens locally against the
3
3
  * issuer's published JWKS, with no per-request network hop.
4
4
  *
5
- * This is the validating half of RFC 9068 (`at+jwt`): the authorization
6
- * server signs access tokens and publishes its public keys at a `jwks_uri`;
7
- * the resource server fetches those keys once, caches them, and verifies
8
- * every subsequent token in-process. Signature verification uses Web Crypto
5
+ * It validates RFC 9068 (`at+jwt`) access tokens: the authorization server
6
+ * signs access tokens and publishes its public keys at a `jwks_uri`; the
7
+ * resource server fetches those keys once, caches them, and verifies every
8
+ * subsequent token in-process, including the `typ` header and every claim
9
+ * RFC 9068 §2.2 makes REQUIRED. Signature verification uses Web Crypto
9
10
  * only — the package takes no JOSE dependency.
10
11
  *
11
12
  * @see https://datatracker.ietf.org/doc/html/rfc9068
@@ -27,19 +28,25 @@ import type { TokenReaderInterface } from "./services/token.ts";
27
28
  export interface JwtAccessTokenClaims {
28
29
  /** Issuer that signed the token. Verified against the configured issuer. */
29
30
  iss: string;
30
- /** Subject — the end user, or the client itself for client-credentials tokens. */
31
- sub?: string;
31
+ /** Subject — the end user, or the client itself for client-credentials tokens. Required. */
32
+ sub: string;
32
33
  /** Audience(s) the token is intended for. Verified against the configured audience. */
33
34
  aud: string | string[];
34
35
  /** Expiration time, seconds since the epoch. */
35
36
  exp: number;
36
- /** Issued-at time, seconds since the epoch. */
37
- iat?: number;
37
+ /** Issued-at time, seconds since the epoch. Required. */
38
+ iat: number;
38
39
  /** Not-before time, seconds since the epoch. */
39
40
  nbf?: number;
40
- /** Unique token identifier. */
41
+ /**
42
+ * Unique token identifier. Required unless
43
+ * {@linkcode JwksTokenReaderOptions.tokenIdClaim} names another claim.
44
+ */
41
45
  jti?: string;
42
- /** Client the token was issued to. */
46
+ /**
47
+ * Client the token was issued to. Required unless
48
+ * {@linkcode JwksTokenReaderOptions.clientIdClaim} names another claim.
49
+ */
43
50
  client_id?: string;
44
51
  /** Space-delimited granted scopes. */
45
52
  scope?: string;
@@ -105,9 +112,18 @@ export interface JwksTokenReaderOptions<Client extends ClientInterface, User, S
105
112
  * §2.2 makes `client_id` REQUIRED, and its absence is what tells an
106
113
  * `id_token` apart from an access token). Defaults to `"client_id"`; point
107
114
  * it at the vendor claim for issuers that deviate — Okta's `cid`, Azure
108
- * AD v1's `appid`. There is deliberately no way to drop the requirement.
115
+ * AD v1's `appid`. The requirement cannot be turned off, but pointing this
116
+ * at a claim every token carries defeats it.
109
117
  */
110
118
  clientIdClaim?: string;
119
+ /**
120
+ * Claim carrying the unique token identifier, required on every accepted
121
+ * token (RFC 9068 §2.2 makes `jti` REQUIRED). Defaults to `"jti"`; point it
122
+ * at the vendor claim for issuers that deviate — Microsoft Entra ID's
123
+ * `uti`. The requirement cannot be turned off, but pointing this at a claim
124
+ * every token carries defeats it.
125
+ */
126
+ tokenIdClaim?: string;
111
127
  /**
112
128
  * Leeway in seconds applied to `exp` and `nbf` to absorb clock drift
113
129
  * between the issuer and this server. Defaults to 30. Must be finite and
@@ -239,7 +255,8 @@ export declare class JwksTokenReader<Client extends ClientInterface, User, S ext
239
255
  #private;
240
256
  /**
241
257
  * Defaults: every Web Crypto asymmetric algorithm accepted, `at+jwt`
242
- * required as `typ`, `client_id` as the required client-id claim, 30
258
+ * required as `typ`, `client_id` as the required client-id claim, `jti` as
259
+ * the required token-identifier claim, 30
243
260
  * seconds of clock skew, a 10-minute cache with a 30-second minimum
244
261
  * between fetches, a 5-second request deadline, a 256 KiB response cap,
245
262
  * {@linkcode BasicScope}, and the global `fetch`.
@@ -2,10 +2,11 @@
2
2
  * Token reader that validates JWT access tokens locally against the
3
3
  * issuer's published JWKS, with no per-request network hop.
4
4
  *
5
- * This is the validating half of RFC 9068 (`at+jwt`): the authorization
6
- * server signs access tokens and publishes its public keys at a `jwks_uri`;
7
- * the resource server fetches those keys once, caches them, and verifies
8
- * every subsequent token in-process. Signature verification uses Web Crypto
5
+ * It validates RFC 9068 (`at+jwt`) access tokens: the authorization server
6
+ * signs access tokens and publishes its public keys at a `jwks_uri`; the
7
+ * resource server fetches those keys once, caches them, and verifies every
8
+ * subsequent token in-process, including the `typ` header and every claim
9
+ * RFC 9068 §2.2 makes REQUIRED. Signature verification uses Web Crypto
9
10
  * only — the package takes no JOSE dependency.
10
11
  *
11
12
  * @see https://datatracker.ietf.org/doc/html/rfc9068
@@ -208,6 +209,7 @@ export class JwksTokenReader {
208
209
  #algorithms;
209
210
  #types;
210
211
  #clientIdClaim;
212
+ #tokenIdClaim;
211
213
  #clockSkewSeconds;
212
214
  #cacheMaxAgeMs;
213
215
  #minFetchIntervalMs;
@@ -225,7 +227,8 @@ export class JwksTokenReader {
225
227
  #inFlight;
226
228
  /**
227
229
  * Defaults: every Web Crypto asymmetric algorithm accepted, `at+jwt`
228
- * required as `typ`, `client_id` as the required client-id claim, 30
230
+ * required as `typ`, `client_id` as the required client-id claim, `jti` as
231
+ * the required token-identifier claim, 30
229
232
  * seconds of clock skew, a 10-minute cache with a 30-second minimum
230
233
  * between fetches, a 5-second request deadline, a 256 KiB response cap,
231
234
  * {@linkcode BasicScope}, and the global `fetch`.
@@ -254,6 +257,7 @@ export class JwksTokenReader {
254
257
  }
255
258
  this.#types = (options.types ?? ["at+jwt"]).map(normalizeType);
256
259
  this.#clientIdClaim = options.clientIdClaim ?? "client_id";
260
+ this.#tokenIdClaim = options.tokenIdClaim ?? "jti";
257
261
  this.#clockSkewSeconds = options.clockSkewSeconds ?? 30;
258
262
  assertClockSkewSeconds(this.#clockSkewSeconds);
259
263
  this.#cacheMaxAgeMs = options.cacheMaxAgeMs ?? 600_000;
@@ -336,9 +340,14 @@ export class JwksTokenReader {
336
340
  #validClaims(claims) {
337
341
  if (claims.iss !== this.#issuer)
338
342
  return false;
339
- const clientId = claims[this.#clientIdClaim];
340
- if (typeof clientId !== "string" || !clientId)
343
+ for (const claim of [this.#clientIdClaim, this.#tokenIdClaim, "sub"]) {
344
+ const value = claims[claim];
345
+ if (typeof value !== "string" || !value)
346
+ return false;
347
+ }
348
+ if (typeof claims.iat !== "number" || !Number.isFinite(claims.iat)) {
341
349
  return false;
350
+ }
342
351
  for (const claim of ID_TOKEN_ONLY_CLAIMS) {
343
352
  if (claims[claim] !== undefined)
344
353
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@udibo/oauth2",
3
- "version": "0.29.0",
3
+ "version": "0.30.0",
4
4
  "description": "A flexible OAuth2 toolkit: framework-agnostic authorization & resource servers, a typed client, Hono adapters (including a backend-for-frontend), and React bindings.",
5
5
  "keywords": [
6
6
  "authorization-server",