@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.
- package/.agents/skills/pkg-jwt-read-jwt-middleware-library/SKILL.md +9 -2
- package/index.d.ts +28 -27
- package/lib/jwtLib.js +79 -52
- package/package.json +4 -4
- package/readme.md +49 -25
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
93
|
+
function validateAndExtractJwtObject(req, verificationJwks, customErrorFunction, options) {
|
|
94
94
|
const jwtString = _validateJwt(req, customErrorFunction);
|
|
95
95
|
|
|
96
|
-
const isJwtSignatureValid = jwtVerifySignedToken(jwtString,
|
|
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(
|
|
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,
|
|
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,
|
|
135
|
+
function validateAndExtractJwtObjectNoThrow(req, verificationJwks, options) {
|
|
130
136
|
_validateAndExtractGenericNoThrow(
|
|
131
137
|
req,
|
|
132
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
177
|
+
verificationJwks,
|
|
172
178
|
serverAuthIntrospector,
|
|
173
179
|
customErrorFunction,
|
|
174
180
|
options,
|
|
175
181
|
) {
|
|
176
|
-
if (!tryValidateAndExtractJwtObject(req,
|
|
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,
|
|
198
|
+
function validateAndExtractWebTokenObjectNoThrow(req, verificationJwks, headerName, options) {
|
|
193
199
|
_validateAndExtractGenericNoThrow(
|
|
194
200
|
req,
|
|
195
|
-
|
|
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,
|
|
211
|
+
function validateAndExtractVisitorObjectNoThrow(req, verificationJwks, options) {
|
|
206
212
|
_validateAndExtractGenericNoThrow(
|
|
207
213
|
req,
|
|
208
|
-
|
|
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,
|
|
224
|
+
function verifyJwtAndRole(role, verificationJwks, customErrorFunction, options) {
|
|
219
225
|
return function (req, res, next) {
|
|
220
226
|
try {
|
|
221
|
-
validateAndExtractJwtObject(req,
|
|
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(
|
|
238
|
+
function verifyJwt(verificationJwks, customErrorFunction, options) {
|
|
233
239
|
return function (req, res, next) {
|
|
234
240
|
try {
|
|
235
|
-
validateAndExtractJwtObject(req,
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
324
|
+
function verifyWebToken(verificationJwks, headerName, customErrorFunction, options) {
|
|
314
325
|
return function (req, res, next) {
|
|
315
326
|
try {
|
|
316
|
-
validateAndExtractWebToken(req,
|
|
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(
|
|
336
|
+
function verifyJwtNoThrow(verificationJwks, options) {
|
|
326
337
|
return function (req, res, next) {
|
|
327
338
|
try {
|
|
328
|
-
validateAndExtractJwtObjectNoThrow(req,
|
|
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(
|
|
348
|
+
function verifyWebTokenNoThrow(verificationJwks, headerName, options) {
|
|
338
349
|
return function (req, res, next) {
|
|
339
350
|
try {
|
|
340
|
-
validateAndExtractWebTokenObjectNoThrow(req,
|
|
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(
|
|
360
|
+
function verifyVisitorNoThrow(verificationJwks, options) {
|
|
350
361
|
return function (req, res, next) {
|
|
351
362
|
try {
|
|
352
|
-
validateAndExtractVisitorObjectNoThrow(req,
|
|
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(
|
|
372
|
+
function verifyUserAuthorization(verificationJwks, customErrorFunction, options) {
|
|
362
373
|
return function (req, res, next) {
|
|
363
374
|
try {
|
|
364
|
-
validateAndExtractUserAuthorizationObject(
|
|
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(
|
|
389
|
+
function verifyUserAuthorizationNoThrow(verificationJwks, options) {
|
|
374
390
|
return function (req, res, next) {
|
|
375
391
|
try {
|
|
376
|
-
validateAndExtractUserAuthorizationObjectNoThrow(req,
|
|
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(
|
|
386
|
-
|
|
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,
|
|
403
|
-
const config = createUserAuthorizationConfig(
|
|
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,
|
|
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,
|
|
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(
|
|
674
|
+
function createUserAuthorizationConfig(verificationJwks, options) {
|
|
654
675
|
return normalizeUserAuthorizationConfig({
|
|
655
676
|
...(options || {}),
|
|
656
|
-
|
|
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
|
-
|
|
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.
|
|
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(
|
|
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,
|
|
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,
|
|
854
|
-
const isValid = await _isJwtSignatureValidNoThrow(jwt,
|
|
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,
|
|
890
|
+
async function _isJwtSignatureValidNoThrow(jwt, verificationJwks) {
|
|
864
891
|
if (jwt && typeof jwt === 'string') {
|
|
865
|
-
return jwtVerifySignedToken(jwt,
|
|
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.
|
|
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.
|
|
54
|
-
"@carecard/common-util": "3.
|
|
55
|
-
"@carecard/validate": "3.
|
|
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
|
-
|
|
57
|
-
const verifyAdmin = verifyJwtAndRole('admin',
|
|
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
|
-
###
|
|
70
|
+
### Bearer JWT Verification
|
|
70
71
|
|
|
71
72
|
```javascript
|
|
72
|
-
const {
|
|
73
|
+
const { jwtVerify } = require('@carecard/jwt-read');
|
|
73
74
|
|
|
74
|
-
|
|
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
|
|
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
|
-
|
|
114
|
+
signingJwk: institutionsSigningJwk,
|
|
117
115
|
});
|
|
118
116
|
|
|
119
|
-
app.use(
|
|
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
|
|
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 {
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|