@carecard/jwt-read 3.25.0 → 3.27.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.
@@ -113,7 +113,7 @@ depend on those folders being present.
113
113
 
114
114
  `lib/jwtLib.js` owns:
115
115
 
116
- - Signature verification using public keys.
116
+ - Signature verification using parsed public Ed25519 JWKS values.
117
117
  - Middleware-like functions for Express, such as `verifyJwtAndRole`.
118
118
  - Service-to-service JWT verification and extraction helpers:
119
119
  `jwtValidateAndExtractService` and `jwtVerifyService`.
@@ -141,6 +141,13 @@ depend on those folders being present.
141
141
  Use `@carecard/auth-util` for JWT creation, decomposition, and signature
142
142
  verification. Do not duplicate cryptographic logic in this package.
143
143
 
144
+ Every verification argument and nested `userAuthorization.verificationJwks`
145
+ option must be a value returned by
146
+ `parseJwtVerificationJwks(serializedJwks)`. Do not accept PEM text, RSA keys,
147
+ raw JWK objects, missing-`kid` tokens, or single-key compatibility fallbacks.
148
+ Verification selects the exact public key named by the token's RFC 7638
149
+ thumbprint `kid`; unknown and retired kids fail closed.
150
+
144
151
  JWT creation functions do not belong in this package. Service-to-service token
145
152
  creation belongs in `@carecard/auth-util` via `jwtCreateServiceToken` and
146
153
  `jwtCreateServiceAuthorizationHeader`.
@@ -153,7 +160,7 @@ JWT context.
153
160
  Service JWTs must follow standard JWT claim semantics. They use `iss` for the
154
161
  sending service, `sub` for the sending service identity, `aud` for the
155
162
  receiving service, and NumericDate `iat`, `exp`, and optional `nbf` claims.
156
- Receivers must verify the signature with the sending service public key and
163
+ Receivers must verify the signature with the sending service public JWKS and
157
164
  must check expected issuer, audience, subject, and lifetime. Do not add
158
165
  CareCard-specific replacement claims when a registered JWT claim covers the
159
166
  same meaning.
package/index.d.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import type { NextFunction, Request, Response } from 'express';
6
+ import type { JwtVerificationJwks } from '@carecard/auth-util';
6
7
 
7
8
  export const DEFAULT_USER_AUTHORIZATION_HEADER_NAME: 'X-Authorization-Context';
8
9
  export const DEFAULT_USER_AUTHORIZATION_MAX_TOKEN_LENGTH: 2048;
@@ -118,7 +119,7 @@ export interface JwtRequestContext {
118
119
  }
119
120
 
120
121
  export interface UserAuthorizationTokenOptions {
121
- publicKey?: string;
122
+ verificationJwks?: JwtVerificationJwks;
122
123
  headerName?: string;
123
124
  maxTokenLength?: number;
124
125
  expectedType?: string;
@@ -166,7 +167,7 @@ export type ServerAuthIntrospector = (
166
167
  * and extracts it into req.jwt. Throws an error if invalid.
167
168
  */
168
169
  export function jwtVerify(
169
- publicKey: string,
170
+ verificationJwks: JwtVerificationJwks,
170
171
  customErrorFunction?: () => void,
171
172
  options?: UserAuthorizationReadOptions,
172
173
  ): (req: AuthenticatedRequest, res: Response, next: NextFunction) => void;
@@ -176,7 +177,7 @@ export function jwtVerify(
176
177
  * Throws an error if invalid.
177
178
  */
178
179
  export function jwtVerifyWebToken(
179
- publicKey: string,
180
+ verificationJwks: JwtVerificationJwks,
180
181
  headerName: string,
181
182
  customErrorFunction?: () => void,
182
183
  options?: UserAuthorizationReadOptions,
@@ -187,7 +188,7 @@ export function jwtVerifyWebToken(
187
188
  * and extracts it into req.jwt. Returns false instead of throwing if invalid.
188
189
  */
189
190
  export function jwtVerifyNoThrow(
190
- publicKey: string,
191
+ verificationJwks: JwtVerificationJwks,
191
192
  options?: UserAuthorizationReadOptions,
192
193
  ): (req: AuthenticatedRequest, res: Response, next: NextFunction) => void;
193
194
 
@@ -196,7 +197,7 @@ export function jwtVerifyNoThrow(
196
197
  * Returns false instead of throwing if invalid.
197
198
  */
198
199
  export function jwtVerifyWebTokenNoThrow(
199
- publicKey: string,
200
+ verificationJwks: JwtVerificationJwks,
200
201
  headerName: string,
201
202
  options?: UserAuthorizationReadOptions,
202
203
  ): (req: AuthenticatedRequest, res: Response, next: NextFunction) => void;
@@ -205,7 +206,7 @@ export function jwtVerifyWebTokenNoThrow(
205
206
  * Returns middleware that verifies X-Authorization-Context and extracts it into req.userAuthorization.
206
207
  */
207
208
  export function jwtVerifyUserAuthorization(
208
- publicKey: string,
209
+ verificationJwks: JwtVerificationJwks,
209
210
  customErrorFunction?: () => void,
210
211
  options?: UserAuthorizationTokenOptions,
211
212
  ): (req: AuthenticatedRequest, res: Response, next: NextFunction) => void;
@@ -214,7 +215,7 @@ export function jwtVerifyUserAuthorization(
214
215
  * Returns middleware that verifies X-Authorization-Context into req.userAuthorization without throwing for invalid tokens.
215
216
  */
216
217
  export function jwtVerifyUserAuthorizationNoThrow(
217
- publicKey: string,
218
+ verificationJwks: JwtVerificationJwks,
218
219
  options?: UserAuthorizationTokenOptions,
219
220
  ): (req: AuthenticatedRequest, res: Response, next: NextFunction) => void;
220
221
 
@@ -223,7 +224,7 @@ export function jwtVerifyUserAuthorizationNoThrow(
223
224
  * and extracts it into req.visitor. Returns false instead of throwing if invalid.
224
225
  */
225
226
  export function jwtVerifyVisitorNoThrow(
226
- publicKey: string,
227
+ verificationJwks: JwtVerificationJwks,
227
228
  options?: UserAuthorizationReadOptions,
228
229
  ): (req: AuthenticatedRequest, res: Response, next: NextFunction) => void;
229
230
 
@@ -254,7 +255,7 @@ export function jwtGetAgeInSeconds(req?: JwtRequestContext): number;
254
255
  */
255
256
  export function jwtVerifyAndHasRole(
256
257
  userRole: string,
257
- publicKey: string,
258
+ verificationJwks: JwtVerificationJwks,
258
259
  customErrorFunction?: () => void,
259
260
  options?: UserAuthorizationReadOptions,
260
261
  ): (req: AuthenticatedRequest, res: Response, next: NextFunction) => void;
@@ -264,7 +265,7 @@ export function jwtVerifyAndHasRole(
264
265
  * Server-auth tokens are validated by the supplied introspector on every request.
265
266
  */
266
267
  export function jwtVerifyOrServerAuth(
267
- publicKey: string,
268
+ verificationJwks: JwtVerificationJwks,
268
269
  serverAuthIntrospector: ServerAuthIntrospector,
269
270
  customErrorFunction?: () => void,
270
271
  options?: UserAuthorizationReadOptions,
@@ -276,7 +277,7 @@ export function jwtVerifyOrServerAuth(
276
277
  */
277
278
  export function jwtVerifyOrServerAuthAndHasRole(
278
279
  userRole: string,
279
- publicKey: string,
280
+ verificationJwks: JwtVerificationJwks,
280
281
  serverAuthIntrospector: ServerAuthIntrospector,
281
282
  customErrorFunction?: () => void,
282
283
  options?: UserAuthorizationReadOptions,
@@ -318,7 +319,7 @@ export function jwtGetContext(req: JwtRequestContext): JwtContext;
318
319
  */
319
320
  export function jwtValidateAndExtract(
320
321
  req: AuthenticatedRequest,
321
- publicKey: string,
322
+ verificationJwks: JwtVerificationJwks,
322
323
  customErrorFunction?: () => void,
323
324
  options?: UserAuthorizationReadOptions,
324
325
  ): void;
@@ -328,7 +329,7 @@ export function jwtValidateAndExtract(
328
329
  */
329
330
  export function jwtValidateAndExtractUserAuthorization(
330
331
  req: AuthenticatedRequest,
331
- publicKey: string,
332
+ verificationJwks: JwtVerificationJwks,
332
333
  customErrorFunction?: () => void,
333
334
  options?: UserAuthorizationTokenOptions,
334
335
  ): void;
@@ -338,7 +339,7 @@ export function jwtValidateAndExtractUserAuthorization(
338
339
  */
339
340
  export function jwtValidateAndExtractUserAuthorizationNoThrow(
340
341
  req: AuthenticatedRequest,
341
- publicKey: string,
342
+ verificationJwks: JwtVerificationJwks,
342
343
  options?: UserAuthorizationTokenOptions,
343
344
  ): void;
344
345
 
@@ -347,7 +348,7 @@ export function jwtValidateAndExtractUserAuthorizationNoThrow(
347
348
  */
348
349
  export function jwtValidateAndExtractService(
349
350
  req: AuthenticatedRequest,
350
- publicKey: string,
351
+ verificationJwks: JwtVerificationJwks,
351
352
  expectedIssuer: string,
352
353
  expectedAudience: string,
353
354
  customErrorFunction?: () => void,
@@ -360,7 +361,7 @@ export function jwtValidateAndExtractService(
360
361
  */
361
362
  export function jwtValidateAndExtractOrServerAuth(
362
363
  req: AuthenticatedRequest,
363
- publicKey: string,
364
+ verificationJwks: JwtVerificationJwks,
364
365
  serverAuthIntrospector: ServerAuthIntrospector,
365
366
  customErrorFunction?: () => void,
366
367
  options?: UserAuthorizationReadOptions,
@@ -371,7 +372,7 @@ export function jwtValidateAndExtractOrServerAuth(
371
372
  */
372
373
  export function jwtValidateAndExtractWebToken(
373
374
  req: AuthenticatedRequest,
374
- publicKey: string,
375
+ verificationJwks: JwtVerificationJwks,
375
376
  headerName: string,
376
377
  customErrorFunction?: () => void,
377
378
  options?: UserAuthorizationReadOptions,
@@ -382,7 +383,7 @@ export function jwtValidateAndExtractWebToken(
382
383
  */
383
384
  export function jwtValidateAndExtractNoThrow(
384
385
  req: AuthenticatedRequest,
385
- publicKey: string,
386
+ verificationJwks: JwtVerificationJwks,
386
387
  options?: UserAuthorizationReadOptions,
387
388
  ): void;
388
389
 
@@ -391,7 +392,7 @@ export function jwtValidateAndExtractNoThrow(
391
392
  */
392
393
  export function jwtValidateAndExtractWebTokenNoThrow(
393
394
  req: AuthenticatedRequest,
394
- publicKey: string,
395
+ verificationJwks: JwtVerificationJwks,
395
396
  headerName: string,
396
397
  options?: UserAuthorizationReadOptions,
397
398
  ): void;
@@ -401,7 +402,7 @@ export function jwtValidateAndExtractWebTokenNoThrow(
401
402
  */
402
403
  export function jwtValidateAndExtractVisitorNoThrow(
403
404
  req: AuthenticatedRequest,
404
- publicKey: string,
405
+ verificationJwks: JwtVerificationJwks,
405
406
  options?: UserAuthorizationReadOptions,
406
407
  ): void;
407
408
 
@@ -409,7 +410,7 @@ export function jwtValidateAndExtractVisitorNoThrow(
409
410
  * Returns middleware that verifies a service-to-service JWT from one expected sender.
410
411
  */
411
412
  export function jwtVerifyService(
412
- publicKey: string,
413
+ verificationJwks: JwtVerificationJwks,
413
414
  expectedIssuer: string,
414
415
  expectedAudience: string,
415
416
  customErrorFunction?: () => void,
@@ -422,7 +423,7 @@ export function jwtVerifyService(
422
423
  * @deprecated use jwtVerify
423
424
  */
424
425
  export function verifyJwt(
425
- publicKey: string,
426
+ verificationJwks: JwtVerificationJwks,
426
427
  customErrorFunction?: () => void,
427
428
  options?: UserAuthorizationReadOptions,
428
429
  ): (req: AuthenticatedRequest, res: Response, next: NextFunction) => void;
@@ -433,7 +434,7 @@ export function verifyJwt(
433
434
  * @deprecated use jwtVerifyWebToken
434
435
  */
435
436
  export function verifyWebToken(
436
- publicKey: string,
437
+ verificationJwks: JwtVerificationJwks,
437
438
  headerName: string,
438
439
  customErrorFunction?: () => void,
439
440
  options?: UserAuthorizationReadOptions,
@@ -445,7 +446,7 @@ export function verifyWebToken(
445
446
  * @deprecated use jwtVerifyNoThrow
446
447
  */
447
448
  export function verifyJwtNoThrow(
448
- publicKey: string,
449
+ verificationJwks: JwtVerificationJwks,
449
450
  options?: UserAuthorizationReadOptions,
450
451
  ): (req: AuthenticatedRequest, res: Response, next: NextFunction) => void;
451
452
 
@@ -455,7 +456,7 @@ export function verifyJwtNoThrow(
455
456
  * @deprecated use jwtVerifyWebTokenNoThrow
456
457
  */
457
458
  export function verifyWebTokenNoThrow(
458
- publicKey: string,
459
+ verificationJwks: JwtVerificationJwks,
459
460
  headerName: string,
460
461
  options?: UserAuthorizationReadOptions,
461
462
  ): (req: AuthenticatedRequest, res: Response, next: NextFunction) => void;
@@ -466,7 +467,7 @@ export function verifyWebTokenNoThrow(
466
467
  * @deprecated use jwtVerifyVisitorNoThrow
467
468
  */
468
469
  export function verifyVisitorNoThrow(
469
- publicKey: string,
470
+ verificationJwks: JwtVerificationJwks,
470
471
  options?: UserAuthorizationReadOptions,
471
472
  ): (req: AuthenticatedRequest, res: Response, next: NextFunction) => void;
472
473
 
@@ -504,7 +505,7 @@ export function jwtAgeInSeconds(req?: JwtRequestContext): number;
504
505
  */
505
506
  export function verifyJwtAndRole(
506
507
  userRole: string,
507
- publicKey: string,
508
+ verificationJwks: JwtVerificationJwks,
508
509
  customErrorFunction?: () => void,
509
510
  options?: UserAuthorizationReadOptions,
510
511
  ): (req: AuthenticatedRequest, res: Response, next: NextFunction) => void;
package/lib/jwtLib.js CHANGED
@@ -90,10 +90,10 @@ function jwtAgeInSeconds(req) {
90
90
  }
91
91
 
92
92
  // Pattern: Decorator - preserves JWT extraction while optionally adding a scoped authorization context.
93
- function validateAndExtractJwtObject(req, publicKey, customErrorFunction, options) {
93
+ function validateAndExtractJwtObject(req, verificationJwks, customErrorFunction, options) {
94
94
  const jwtString = _validateJwt(req, customErrorFunction);
95
95
 
96
- const isJwtSignatureValid = jwtVerifySignedToken(jwtString, publicKey);
96
+ const isJwtSignatureValid = jwtVerifySignedToken(jwtString, verificationJwks);
97
97
 
98
98
  if (jwtString && isJwtSignatureValid) {
99
99
  _extractJwtObject(req, jwtString, customErrorFunction);
@@ -108,10 +108,16 @@ function validateAndExtractJwtObject(req, publicKey, customErrorFunction, option
108
108
  }
109
109
 
110
110
  // Pattern: Decorator - keeps custom-header JWT behavior and optionally reads user authorization context.
111
- function validateAndExtractWebToken(req, publicKey, headerName, customErrorFunction, options) {
111
+ function validateAndExtractWebToken(
112
+ req,
113
+ verificationJwks,
114
+ headerName,
115
+ customErrorFunction,
116
+ options,
117
+ ) {
112
118
  const webTokenString = _validateWebToken(req, headerName, customErrorFunction);
113
119
 
114
- const isJwtSignatureValid = jwtVerifySignedToken(webTokenString, publicKey);
120
+ const isJwtSignatureValid = jwtVerifySignedToken(webTokenString, verificationJwks);
115
121
 
116
122
  if (webTokenString && isJwtSignatureValid) {
117
123
  _extractJwtObject(req, webTokenString, customErrorFunction);
@@ -126,10 +132,10 @@ function validateAndExtractWebToken(req, publicKey, headerName, customErrorFunct
126
132
  }
127
133
 
128
134
  // Pattern: Decorator - extends no-throw JWT extraction without changing req.jwt failure semantics.
129
- function validateAndExtractJwtObjectNoThrow(req, publicKey, options) {
135
+ function validateAndExtractJwtObjectNoThrow(req, verificationJwks, options) {
130
136
  _validateAndExtractGenericNoThrow(
131
137
  req,
132
- publicKey,
138
+ verificationJwks,
133
139
  _validateJwtNoThrow,
134
140
  _extractJwtObjectNoThrow,
135
141
  'jwt',
@@ -141,14 +147,14 @@ function validateAndExtractJwtObjectNoThrow(req, publicKey, options) {
141
147
  // Pattern: Decorator - keeps service-JWT checks distinct from optional user authorization context.
142
148
  function validateAndExtractServiceJwtObject(
143
149
  req,
144
- publicKey,
150
+ verificationJwks,
145
151
  expectedIssuer,
146
152
  expectedAudience,
147
153
  customErrorFunction,
148
154
  options,
149
155
  ) {
150
156
  const jwtString = _validateJwt(req, customErrorFunction);
151
- const isJwtSignatureValid = jwtVerifySignedToken(jwtString, publicKey);
157
+ const isJwtSignatureValid = jwtVerifySignedToken(jwtString, verificationJwks);
152
158
 
153
159
  if (jwtString && isJwtSignatureValid) {
154
160
  _extractJwtObject(req, jwtString, customErrorFunction);
@@ -168,12 +174,12 @@ function validateAndExtractServiceJwtObject(
168
174
  // Pattern: Decorator - normalizes primary auth first, then applies optional scoped authorization.
169
175
  async function validateAndExtractJwtOrServerAuthObject(
170
176
  req,
171
- publicKey,
177
+ verificationJwks,
172
178
  serverAuthIntrospector,
173
179
  customErrorFunction,
174
180
  options,
175
181
  ) {
176
- if (!tryValidateAndExtractJwtObject(req, publicKey)) {
182
+ if (!tryValidateAndExtractJwtObject(req, verificationJwks)) {
177
183
  const serverAuthToken = _validateServerAuthToken(req, customErrorFunction);
178
184
  const claims = await introspectServerAuthToken(
179
185
  serverAuthIntrospector,
@@ -189,10 +195,10 @@ async function validateAndExtractJwtOrServerAuthObject(
189
195
  }
190
196
 
191
197
  // Pattern: Decorator - extends custom-header no-throw extraction with optional user authorization context.
192
- function validateAndExtractWebTokenObjectNoThrow(req, publicKey, headerName, options) {
198
+ function validateAndExtractWebTokenObjectNoThrow(req, verificationJwks, headerName, options) {
193
199
  _validateAndExtractGenericNoThrow(
194
200
  req,
195
- publicKey,
201
+ verificationJwks,
196
202
  r => _validateWebTokenNoThrow(r, headerName),
197
203
  _extractJwtObjectNoThrow,
198
204
  'jwt',
@@ -202,10 +208,10 @@ function validateAndExtractWebTokenObjectNoThrow(req, publicKey, headerName, opt
202
208
  }
203
209
 
204
210
  // Pattern: Decorator - keeps visitor token extraction independent from optional user authorization context.
205
- function validateAndExtractVisitorObjectNoThrow(req, publicKey, options) {
211
+ function validateAndExtractVisitorObjectNoThrow(req, verificationJwks, options) {
206
212
  _validateAndExtractGenericNoThrow(
207
213
  req,
208
- publicKey,
214
+ verificationJwks,
209
215
  _validateVisitorNoThrow,
210
216
  _extractVisitorObjectNoThrow,
211
217
  'visitor',
@@ -215,10 +221,10 @@ function validateAndExtractVisitorObjectNoThrow(req, publicKey, options) {
215
221
  }
216
222
 
217
223
  // Pattern: Middleware - composes JWT verification, optional user authorization, and role checks.
218
- function verifyJwtAndRole(role, publicKey, customErrorFunction, options) {
224
+ function verifyJwtAndRole(role, verificationJwks, customErrorFunction, options) {
219
225
  return function (req, res, next) {
220
226
  try {
221
- validateAndExtractJwtObject(req, publicKey, customErrorFunction, options);
227
+ validateAndExtractJwtObject(req, verificationJwks, customErrorFunction, options);
222
228
  const isRoleExist = doesJwtUserHasRole(req, role);
223
229
  _isLoginRequired(isRoleExist, customErrorFunction);
224
230
  next();
@@ -229,10 +235,10 @@ function verifyJwtAndRole(role, publicKey, customErrorFunction, options) {
229
235
  }
230
236
 
231
237
  // Pattern: Middleware - verifies the primary JWT and optionally attaches user authorization context.
232
- function verifyJwt(publicKey, customErrorFunction, options) {
238
+ function verifyJwt(verificationJwks, customErrorFunction, options) {
233
239
  return function (req, res, next) {
234
240
  try {
235
- validateAndExtractJwtObject(req, publicKey, customErrorFunction, options);
241
+ validateAndExtractJwtObject(req, verificationJwks, customErrorFunction, options);
236
242
  next();
237
243
  } catch (err) {
238
244
  next(err);
@@ -242,7 +248,7 @@ function verifyJwt(publicKey, customErrorFunction, options) {
242
248
 
243
249
  // Pattern: Middleware - verifies service identity and optionally carries user authorization context.
244
250
  function verifyServiceJwt(
245
- publicKey,
251
+ verificationJwks,
246
252
  expectedIssuer,
247
253
  expectedAudience,
248
254
  customErrorFunction,
@@ -252,7 +258,7 @@ function verifyServiceJwt(
252
258
  try {
253
259
  validateAndExtractServiceJwtObject(
254
260
  req,
255
- publicKey,
261
+ verificationJwks,
256
262
  expectedIssuer,
257
263
  expectedAudience,
258
264
  customErrorFunction,
@@ -266,12 +272,17 @@ function verifyServiceJwt(
266
272
  }
267
273
 
268
274
  // Pattern: Middleware - accepts JWT or server-auth and then optionally verifies scoped authorization.
269
- function verifyJwtOrServerAuth(publicKey, serverAuthIntrospector, customErrorFunction, options) {
275
+ function verifyJwtOrServerAuth(
276
+ verificationJwks,
277
+ serverAuthIntrospector,
278
+ customErrorFunction,
279
+ options,
280
+ ) {
270
281
  return async function (req, res, next) {
271
282
  try {
272
283
  await validateAndExtractJwtOrServerAuthObject(
273
284
  req,
274
- publicKey,
285
+ verificationJwks,
275
286
  serverAuthIntrospector,
276
287
  customErrorFunction,
277
288
  options,
@@ -286,7 +297,7 @@ function verifyJwtOrServerAuth(publicKey, serverAuthIntrospector, customErrorFun
286
297
  // Pattern: Middleware - composes flexible auth, optional user authorization, and role checks.
287
298
  function verifyJwtOrServerAuthAndHasRole(
288
299
  role,
289
- publicKey,
300
+ verificationJwks,
290
301
  serverAuthIntrospector,
291
302
  customErrorFunction,
292
303
  options,
@@ -295,7 +306,7 @@ function verifyJwtOrServerAuthAndHasRole(
295
306
  try {
296
307
  await validateAndExtractJwtOrServerAuthObject(
297
308
  req,
298
- publicKey,
309
+ verificationJwks,
299
310
  serverAuthIntrospector,
300
311
  customErrorFunction,
301
312
  options,
@@ -310,10 +321,10 @@ function verifyJwtOrServerAuthAndHasRole(
310
321
  }
311
322
 
312
323
  // Pattern: Middleware - verifies a custom-header JWT and optionally scoped user authorization.
313
- function verifyWebToken(publicKey, headerName, customErrorFunction, options) {
324
+ function verifyWebToken(verificationJwks, headerName, customErrorFunction, options) {
314
325
  return function (req, res, next) {
315
326
  try {
316
- validateAndExtractWebToken(req, publicKey, headerName, customErrorFunction, options);
327
+ validateAndExtractWebToken(req, verificationJwks, headerName, customErrorFunction, options);
317
328
  next();
318
329
  } catch (err) {
319
330
  next(err);
@@ -322,10 +333,10 @@ function verifyWebToken(publicKey, headerName, customErrorFunction, options) {
322
333
  }
323
334
 
324
335
  // Pattern: Middleware - preserves no-throw JWT behavior while clearing invalid optional context.
325
- function verifyJwtNoThrow(publicKey, options) {
336
+ function verifyJwtNoThrow(verificationJwks, options) {
326
337
  return function (req, res, next) {
327
338
  try {
328
- validateAndExtractJwtObjectNoThrow(req, publicKey, options);
339
+ validateAndExtractJwtObjectNoThrow(req, verificationJwks, options);
329
340
  next();
330
341
  } catch (err) {
331
342
  next(err);
@@ -334,10 +345,10 @@ function verifyJwtNoThrow(publicKey, options) {
334
345
  }
335
346
 
336
347
  // Pattern: Middleware - preserves custom-header no-throw behavior with optional context extraction.
337
- function verifyWebTokenNoThrow(publicKey, headerName, options) {
348
+ function verifyWebTokenNoThrow(verificationJwks, headerName, options) {
338
349
  return function (req, res, next) {
339
350
  try {
340
- validateAndExtractWebTokenObjectNoThrow(req, publicKey, headerName, options);
351
+ validateAndExtractWebTokenObjectNoThrow(req, verificationJwks, headerName, options);
341
352
  next();
342
353
  } catch (err) {
343
354
  next(err);
@@ -346,10 +357,10 @@ function verifyWebTokenNoThrow(publicKey, headerName, options) {
346
357
  }
347
358
 
348
359
  // Pattern: Middleware - keeps visitor extraction no-throw and independently reads optional context.
349
- function verifyVisitorNoThrow(publicKey, options) {
360
+ function verifyVisitorNoThrow(verificationJwks, options) {
350
361
  return function (req, res, next) {
351
362
  try {
352
- validateAndExtractVisitorObjectNoThrow(req, publicKey, options);
363
+ validateAndExtractVisitorObjectNoThrow(req, verificationJwks, options);
353
364
  next();
354
365
  } catch (err) {
355
366
  next(err);
@@ -358,10 +369,15 @@ function verifyVisitorNoThrow(publicKey, options) {
358
369
  }
359
370
 
360
371
  // Pattern: Middleware - verifies only the scoped user authorization token.
361
- function verifyUserAuthorization(publicKey, customErrorFunction, options) {
372
+ function verifyUserAuthorization(verificationJwks, customErrorFunction, options) {
362
373
  return function (req, res, next) {
363
374
  try {
364
- validateAndExtractUserAuthorizationObject(req, publicKey, customErrorFunction, options);
375
+ validateAndExtractUserAuthorizationObject(
376
+ req,
377
+ verificationJwks,
378
+ customErrorFunction,
379
+ options,
380
+ );
365
381
  next();
366
382
  } catch (err) {
367
383
  next(err);
@@ -370,10 +386,10 @@ function verifyUserAuthorization(publicKey, customErrorFunction, options) {
370
386
  }
371
387
 
372
388
  // Pattern: Middleware - reads scoped user authorization without throwing for invalid tokens.
373
- function verifyUserAuthorizationNoThrow(publicKey, options) {
389
+ function verifyUserAuthorizationNoThrow(verificationJwks, options) {
374
390
  return function (req, res, next) {
375
391
  try {
376
- validateAndExtractUserAuthorizationObjectNoThrow(req, publicKey, options);
392
+ validateAndExtractUserAuthorizationObjectNoThrow(req, verificationJwks, options);
377
393
  next();
378
394
  } catch (err) {
379
395
  next(err);
@@ -382,8 +398,13 @@ function verifyUserAuthorizationNoThrow(publicKey, options) {
382
398
  }
383
399
 
384
400
  // Pattern: Single Responsibility - validates and attaches only the scoped user authorization token.
385
- function validateAndExtractUserAuthorizationObject(req, publicKey, customErrorFunction, options) {
386
- const config = createUserAuthorizationConfig(publicKey, options);
401
+ function validateAndExtractUserAuthorizationObject(
402
+ req,
403
+ verificationJwks,
404
+ customErrorFunction,
405
+ options,
406
+ ) {
407
+ const config = createUserAuthorizationConfig(verificationJwks, options);
387
408
  const header = readUserAuthorizationHeader(req, config);
388
409
 
389
410
  if (header.present && isUserAuthorizationTokenAllowed(header.token, config)) {
@@ -399,8 +420,8 @@ function validateAndExtractUserAuthorizationObject(req, publicKey, customErrorFu
399
420
  }
400
421
 
401
422
  // Pattern: Single Responsibility - clears invalid scoped authorization without changing primary auth state.
402
- function validateAndExtractUserAuthorizationObjectNoThrow(req, publicKey, options) {
403
- const config = createUserAuthorizationConfig(publicKey, options);
423
+ function validateAndExtractUserAuthorizationObjectNoThrow(req, verificationJwks, options) {
424
+ const config = createUserAuthorizationConfig(verificationJwks, options);
404
425
  const header = readUserAuthorizationHeader(req, config);
405
426
 
406
427
  if (header.present && isUserAuthorizationTokenAllowed(header.token, config)) {
@@ -466,13 +487,13 @@ function _isLoginRequired(hasRequiredRole, customErrorFunction) {
466
487
  }
467
488
 
468
489
  // Pattern: Fail-Closed Probe - accepts a bearer JWT only when signature and lifetime are valid.
469
- function tryValidateAndExtractJwtObject(req, publicKey) {
490
+ function tryValidateAndExtractJwtObject(req, verificationJwks) {
470
491
  const jwtString = _validateJwtNoThrow(req);
471
492
  if (!jwtString || !isJwtString(jwtString)) {
472
493
  return false;
473
494
  }
474
495
 
475
- const isJwtSignatureValid = jwtVerifySignedToken(jwtString, publicKey);
496
+ const isJwtSignatureValid = jwtVerifySignedToken(jwtString, verificationJwks);
476
497
  if (!isJwtSignatureValid) {
477
498
  req.jwt = null;
478
499
  return false;
@@ -650,10 +671,10 @@ function requireActiveExtractedToken(req, propertyName, customErrorFunction) {
650
671
  }
651
672
 
652
673
  // Pattern: Factory - normalizes direct user authorization options into one config shape.
653
- function createUserAuthorizationConfig(publicKey, options) {
674
+ function createUserAuthorizationConfig(verificationJwks, options) {
654
675
  return normalizeUserAuthorizationConfig({
655
676
  ...(options || {}),
656
- publicKey,
677
+ verificationJwks,
657
678
  });
658
679
  }
659
680
 
@@ -668,7 +689,7 @@ function createOptionalUserAuthorizationConfig(options) {
668
689
  // Pattern: Pure Function - centralizes defaults for scoped authorization header verification.
669
690
  function normalizeUserAuthorizationConfig(options) {
670
691
  return {
671
- publicKey: options?.publicKey,
692
+ verificationJwks: options?.verificationJwks,
672
693
  headerName: options?.headerName || DEFAULT_USER_AUTHORIZATION_HEADER_NAME,
673
694
  maxTokenLength: normalizePositiveInteger(
674
695
  options?.maxTokenLength,
@@ -717,7 +738,7 @@ function getRequestHeader(req, headerName) {
717
738
 
718
739
  // Pattern: Single Responsibility - verifies signature and registered JWT time claims for user authorization.
719
740
  function isUserAuthorizationTokenAllowed(token, config) {
720
- if (!token || !config.publicKey || !jwtVerifySignedToken(token, config.publicKey)) {
741
+ if (!token || !config.verificationJwks || !jwtVerifySignedToken(token, config.verificationJwks)) {
721
742
  return false;
722
743
  }
723
744
 
@@ -777,10 +798,16 @@ function _validateGeneric(req, headerName, extractor, customErrorFunction) {
777
798
  }
778
799
 
779
800
  // Pattern: Fail-Closed Extraction - optional tokens survive only valid signatures and lifetimes.
780
- function _validateAndExtractGenericNoThrow(req, publicKey, validator, extractor, propertyName) {
801
+ function _validateAndExtractGenericNoThrow(
802
+ req,
803
+ verificationJwks,
804
+ validator,
805
+ extractor,
806
+ propertyName,
807
+ ) {
781
808
  try {
782
809
  const jwtString = validator(req);
783
- const isJwtSignatureValid = jwtVerifySignedToken(jwtString, publicKey);
810
+ const isJwtSignatureValid = jwtVerifySignedToken(jwtString, verificationJwks);
784
811
 
785
812
  if (jwtString && isJwtSignatureValid) {
786
813
  extractor(req, jwtString);
@@ -850,8 +877,8 @@ function _attachVisitorMethods(visitorObj) {
850
877
  }
851
878
  }
852
879
 
853
- async function _isJwtSignatureValid(jwt, publicKey, customErrorFunction) {
854
- const isValid = await _isJwtSignatureValidNoThrow(jwt, publicKey);
880
+ async function _isJwtSignatureValid(jwt, verificationJwks, customErrorFunction) {
881
+ const isValid = await _isJwtSignatureValidNoThrow(jwt, verificationJwks);
855
882
 
856
883
  if (isValid) {
857
884
  return isValid;
@@ -860,9 +887,9 @@ async function _isJwtSignatureValid(jwt, publicKey, customErrorFunction) {
860
887
  }
861
888
  }
862
889
 
863
- async function _isJwtSignatureValidNoThrow(jwt, publicKey) {
890
+ async function _isJwtSignatureValidNoThrow(jwt, verificationJwks) {
864
891
  if (jwt && typeof jwt === 'string') {
865
- return jwtVerifySignedToken(jwt, publicKey);
892
+ return jwtVerifySignedToken(jwt, verificationJwks);
866
893
  } else {
867
894
  return false;
868
895
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carecard/jwt-read",
3
- "version": "3.25.0",
3
+ "version": "3.27.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/CareCard-ca/pkg-jwt-read.git"
@@ -50,9 +50,9 @@
50
50
  "typescript": "6.0.3"
51
51
  },
52
52
  "dependencies": {
53
- "@carecard/auth-util": "3.25.0",
54
- "@carecard/common-util": "3.25.0",
55
- "@carecard/validate": "3.25.0"
53
+ "@carecard/auth-util": "3.27.0",
54
+ "@carecard/common-util": "3.27.0",
55
+ "@carecard/validate": "3.27.0"
56
56
  },
57
57
  "overrides": {
58
58
  "diff": "8.0.4",
package/readme.md CHANGED
@@ -52,9 +52,10 @@ npm install @carecard/jwt-read
52
52
 
53
53
  ```javascript
54
54
  const { verifyJwtAndRole, throwUsedTokenError } = require('@carecard/jwt-read');
55
+ const { parseJwtVerificationJwks } = require('@carecard/auth-util');
55
56
 
56
- // Create a verification function for 'admin' role
57
- const verifyAdmin = verifyJwtAndRole('admin', publicKey, throwUsedTokenError);
57
+ const verificationJwks = parseJwtVerificationJwks(process.env.MS_AUTH_JWT_VERIFICATION_JWKS);
58
+ const verifyAdmin = verifyJwtAndRole('admin', verificationJwks, throwUsedTokenError);
58
59
 
59
60
  // In an Express controller/middleware
60
61
  try {
@@ -66,15 +67,12 @@ try {
66
67
  }
67
68
  ```
68
69
 
69
- ### Direct JWT Reading
70
+ ### Bearer JWT Verification
70
71
 
71
72
  ```javascript
72
- const { verifyJwt, isJwtExpired } = require('@carecard/jwt-read');
73
+ const { jwtVerify } = require('@carecard/jwt-read');
73
74
 
74
- const result = verifyJwt(rawJwt, publicKey);
75
- if (result && !isJwtExpired(result)) {
76
- console.log('JWT is valid and not expired:', result.payload);
77
- }
75
+ app.use(jwtVerify(verificationJwks));
78
76
  ```
79
77
 
80
78
  ### Role Utilities
@@ -103,8 +101,8 @@ Access level is conveyed by route middleware and endpoint placement, not by
103
101
 
104
102
  Use service JWT verification helpers for backend service calls. The sending
105
103
  service signs the token with `@carecard/auth-util`. The receiving service uses
106
- this package to verify the token with the sending service public key and check
107
- the expected issuer and audience.
104
+ this package to verify the token by `kid` through the sending service's public
105
+ JWKS and check the expected issuer and audience.
108
106
 
109
107
  ```javascript
110
108
  const { jwtCreateServiceAuthorizationHeader } = require('@carecard/auth-util');
@@ -113,10 +111,17 @@ const { jwtVerifyService } = require('@carecard/jwt-read');
113
111
  const authorization = jwtCreateServiceAuthorizationHeader({
114
112
  issuer: 'ms-institutions',
115
113
  audience: 'ms-auth',
116
- privateKey: institutionsPrivateKey,
114
+ signingJwk: institutionsSigningJwk,
117
115
  });
118
116
 
119
- app.use(jwtVerifyService(institutionsPublicKey, 'ms-institutions', 'ms-auth', throwNotAuthorizedError));
117
+ app.use(
118
+ jwtVerifyService(
119
+ institutionsVerificationJwks,
120
+ 'ms-institutions',
121
+ 'ms-auth',
122
+ throwNotAuthorizedError,
123
+ ),
124
+ );
120
125
  ```
121
126
 
122
127
  Service JWT payloads follow standard JWT semantics:
@@ -131,19 +136,27 @@ Service JWT payloads follow standard JWT semantics:
131
136
 
132
137
  Use the `OrServerAuth` helpers on app-facing `ms-*` routes that should accept
133
138
  both current authentication modes. The JWT path verifies locally with the
134
- `ms-auth` public key. The server-auth path calls the provided introspector,
139
+ `ms-auth` public JWKS. The server-auth path calls the provided introspector,
135
140
  which should send the opaque token to
136
141
  `POST /api/v1/ms-auth/server-auth/introspect` with the receiving service's
137
142
  service JWT.
138
143
 
139
144
  ```javascript
140
- const { jwtGetRoleCode, jwtVerifyOrServerAuth, jwtVerifyOrServerAuthAndHasRole } = require('@carecard/jwt-read');
141
-
142
- const verifyUser = jwtVerifyOrServerAuth(msAuthPublicKey, token => introspectServerAuthTokenWithMsAuth(token), throwNotAuthorizedError);
145
+ const {
146
+ jwtGetRoleCode,
147
+ jwtVerifyOrServerAuth,
148
+ jwtVerifyOrServerAuthAndHasRole,
149
+ } = require('@carecard/jwt-read');
150
+
151
+ const verifyUser = jwtVerifyOrServerAuth(
152
+ msAuthVerificationJwks,
153
+ token => introspectServerAuthTokenWithMsAuth(token),
154
+ throwNotAuthorizedError,
155
+ );
143
156
 
144
157
  const verifyAdmin = jwtVerifyOrServerAuthAndHasRole(
145
158
  jwtGetRoleCode('admin'),
146
- msAuthPublicKey,
159
+ msAuthVerificationJwks,
147
160
  token => introspectServerAuthTokenWithMsAuth(token),
148
161
  throwNotAuthorizedError,
149
162
  );
@@ -171,7 +184,7 @@ local limit.
171
184
  const { jwtVerifyUserAuthorization } = require('@carecard/jwt-read');
172
185
 
173
186
  app.use(
174
- jwtVerifyUserAuthorization(institutionsPublicKey, throwNotAuthorizedError, {
187
+ jwtVerifyUserAuthorization(institutionsVerificationJwks, throwNotAuthorizedError, {
175
188
  expectedType: 'carecard.authorization-context.scoped.v1',
176
189
  expectedIssuer: 'ms-institutions',
177
190
  expectedAudience: 'ms-documents',
@@ -184,16 +197,27 @@ optional trailing options object. This preserves current `req.jwt` behavior and
184
197
  adds decoded scoped claims to `req.userAuthorization`.
185
198
 
186
199
  ```javascript
187
- const verifyUser = jwtVerifyOrServerAuth(msAuthPublicKey, token => introspectServerAuthTokenWithMsAuth(token), throwNotAuthorizedError, {
188
- userAuthorization: {
189
- publicKey: institutionsPublicKey,
190
- expectedType: 'carecard.authorization-context.scoped.v1',
191
- expectedIssuer: 'ms-institutions',
192
- expectedAudience: 'ms-documents',
200
+ const verifyUser = jwtVerifyOrServerAuth(
201
+ msAuthVerificationJwks,
202
+ token => introspectServerAuthTokenWithMsAuth(token),
203
+ throwNotAuthorizedError,
204
+ {
205
+ userAuthorization: {
206
+ verificationJwks: institutionsVerificationJwks,
207
+ expectedType: 'carecard.authorization-context.scoped.v1',
208
+ expectedIssuer: 'ms-institutions',
209
+ expectedAudience: 'ms-documents',
210
+ },
193
211
  },
194
- });
212
+ );
195
213
  ```
196
214
 
215
+ Every verification value must come from
216
+ `parseJwtVerificationJwks(serializedJwks)`. A JWKS may contain active and
217
+ retiring public Ed25519 keys, so rotation adds the replacement verifier before
218
+ switching signers and removes the retiring key only after the maximum JWT
219
+ lifetime plus clock skew. Unknown or removed `kid` values fail closed.
220
+
197
221
  When the optional reader is configured, a missing `X-Authorization-Context`
198
222
  leaves `req.userAuthorization` as `null`. If the header is present but invalid,
199
223
  throwing middleware fails closed. No-throw middleware clears