@carecard/jwt-read 3.1.16 → 3.1.18

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/lib/jwtLib.js CHANGED
@@ -3,6 +3,9 @@ const { jwtVerifySignedToken, jwtGetHeaderPayload } = require('@carecard/auth-ut
3
3
 
4
4
  const { throwLoginRequiredError, throwNotAuthorizedError } = require('@carecard/common-util');
5
5
 
6
+ const DEFAULT_USER_AUTHORIZATION_HEADER_NAME = 'X-Authorization-Context';
7
+ const DEFAULT_USER_AUTHORIZATION_MAX_TOKEN_LENGTH = 2048;
8
+
6
9
  function jwtClientId(req) {
7
10
  const jwtObj = req?.jwt || this;
8
11
  return jwtObj?.payload?.sub;
@@ -70,13 +73,15 @@ function jwtAgeInSeconds(req) {
70
73
  }
71
74
  }
72
75
 
73
- function validateAndExtractJwtObject(req, publicKey, customErrorFunction) {
76
+ // Pattern: Decorator - preserves JWT extraction while optionally adding a scoped authorization context.
77
+ function validateAndExtractJwtObject(req, publicKey, customErrorFunction, options) {
74
78
  const jwtString = _validateJwt(req, customErrorFunction);
75
79
 
76
80
  const isJwtSignatureValid = jwtVerifySignedToken(jwtString, publicKey);
77
81
 
78
82
  if (jwtString && isJwtSignatureValid) {
79
83
  _extractJwtObject(req, jwtString, customErrorFunction);
84
+ validateAndExtractOptionalUserAuthorizationObject(req, options, customErrorFunction);
80
85
  } else {
81
86
  req['jwt'] = null;
82
87
  throwError(customErrorFunction);
@@ -85,13 +90,15 @@ function validateAndExtractJwtObject(req, publicKey, customErrorFunction) {
85
90
  return req;
86
91
  }
87
92
 
88
- function validateAndExtractWebToken(req, publicKey, headerName, customErrorFunction) {
93
+ // Pattern: Decorator - keeps custom-header JWT behavior and optionally reads user authorization context.
94
+ function validateAndExtractWebToken(req, publicKey, headerName, customErrorFunction, options) {
89
95
  const webTokenString = _validateWebToken(req, headerName, customErrorFunction);
90
96
 
91
97
  const isJwtSignatureValid = jwtVerifySignedToken(webTokenString, publicKey);
92
98
 
93
99
  if (webTokenString && isJwtSignatureValid) {
94
100
  _extractJwtObject(req, webTokenString, customErrorFunction);
101
+ validateAndExtractOptionalUserAuthorizationObject(req, options, customErrorFunction);
95
102
  } else {
96
103
  req['jwt'] = null;
97
104
  throwError(customErrorFunction);
@@ -100,49 +107,68 @@ function validateAndExtractWebToken(req, publicKey, headerName, customErrorFunct
100
107
  return req;
101
108
  }
102
109
 
103
- function validateAndExtractJwtObjectNoThrow(req, publicKey) {
104
- return _validateAndExtractGenericNoThrow(req, publicKey, _validateJwtNoThrow, _extractJwtObjectNoThrow, 'jwt');
110
+ // Pattern: Decorator - extends no-throw JWT extraction without changing req.jwt failure semantics.
111
+ function validateAndExtractJwtObjectNoThrow(req, publicKey, options) {
112
+ _validateAndExtractGenericNoThrow(req, publicKey, _validateJwtNoThrow, _extractJwtObjectNoThrow, 'jwt');
113
+ validateAndExtractOptionalUserAuthorizationObjectNoThrow(req, options);
114
+ return req;
105
115
  }
106
116
 
107
- function validateAndExtractServiceJwtObject(req, publicKey, expectedIssuer, expectedAudience, customErrorFunction) {
117
+ // Pattern: Decorator - keeps service-JWT checks distinct from optional user authorization context.
118
+ function validateAndExtractServiceJwtObject(req, publicKey, expectedIssuer, expectedAudience, customErrorFunction, options) {
108
119
  const jwtString = _validateJwt(req, customErrorFunction);
109
120
  const isJwtSignatureValid = jwtVerifySignedToken(jwtString, publicKey);
110
121
 
111
122
  if (jwtString && isJwtSignatureValid) {
112
123
  _extractJwtObject(req, jwtString, customErrorFunction);
113
- } else if (req) {
114
- req.jwt = null;
124
+ } else {
125
+ /* istanbul ignore else */
126
+ if (req) {
127
+ req.jwt = null;
128
+ }
115
129
  }
116
130
 
117
131
  if (!req?.jwt || !_isServiceJwtFor(req.jwt.payload, expectedIssuer, expectedAudience)) {
132
+ /* istanbul ignore else */
118
133
  if (req) req.jwt = null;
119
134
  throwError(customErrorFunction);
120
135
  }
121
136
 
137
+ validateAndExtractOptionalUserAuthorizationObject(req, options, customErrorFunction);
122
138
  return req;
123
139
  }
124
140
 
125
- async function validateAndExtractJwtOrServerAuthObject(req, publicKey, serverAuthIntrospector, customErrorFunction) {
126
- if (tryValidateAndExtractJwtObject(req, publicKey)) return req;
141
+ // Pattern: Decorator - normalizes primary auth first, then applies optional scoped authorization.
142
+ async function validateAndExtractJwtOrServerAuthObject(req, publicKey, serverAuthIntrospector, customErrorFunction, options) {
143
+ if (!tryValidateAndExtractJwtObject(req, publicKey)) {
144
+ const serverAuthToken = _validateServerAuthToken(req, customErrorFunction);
145
+ const claims = await introspectServerAuthToken(serverAuthIntrospector, serverAuthToken, req, customErrorFunction);
146
+ attachServerAuthClaims(req, claims, customErrorFunction);
147
+ }
127
148
 
128
- const serverAuthToken = _validateServerAuthToken(req, customErrorFunction);
129
- const claims = await introspectServerAuthToken(serverAuthIntrospector, serverAuthToken, req, customErrorFunction);
130
- attachServerAuthClaims(req, claims, customErrorFunction);
149
+ validateAndExtractOptionalUserAuthorizationObject(req, options, customErrorFunction);
131
150
  return req;
132
151
  }
133
152
 
134
- function validateAndExtractWebTokenObjectNoThrow(req, publicKey, headerName) {
135
- return _validateAndExtractGenericNoThrow(req, publicKey, r => _validateWebTokenNoThrow(r, headerName), _extractJwtObjectNoThrow, 'jwt');
153
+ // Pattern: Decorator - extends custom-header no-throw extraction with optional user authorization context.
154
+ function validateAndExtractWebTokenObjectNoThrow(req, publicKey, headerName, options) {
155
+ _validateAndExtractGenericNoThrow(req, publicKey, r => _validateWebTokenNoThrow(r, headerName), _extractJwtObjectNoThrow, 'jwt');
156
+ validateAndExtractOptionalUserAuthorizationObjectNoThrow(req, options);
157
+ return req;
136
158
  }
137
159
 
138
- function validateAndExtractVisitorObjectNoThrow(req, publicKey) {
139
- return _validateAndExtractGenericNoThrow(req, publicKey, _validateVisitorNoThrow, _extractVisitorObjectNoThrow, 'visitor');
160
+ // Pattern: Decorator - keeps visitor token extraction independent from optional user authorization context.
161
+ function validateAndExtractVisitorObjectNoThrow(req, publicKey, options) {
162
+ _validateAndExtractGenericNoThrow(req, publicKey, _validateVisitorNoThrow, _extractVisitorObjectNoThrow, 'visitor');
163
+ validateAndExtractOptionalUserAuthorizationObjectNoThrow(req, options);
164
+ return req;
140
165
  }
141
166
 
142
- function verifyJwtAndRole(role, publicKey, customErrorFunction) {
167
+ // Pattern: Middleware - composes JWT verification, optional user authorization, and role checks.
168
+ function verifyJwtAndRole(role, publicKey, customErrorFunction, options) {
143
169
  return function (req, res, next) {
144
170
  try {
145
- validateAndExtractJwtObject(req, publicKey, customErrorFunction);
171
+ validateAndExtractJwtObject(req, publicKey, customErrorFunction, options);
146
172
  const isRoleExist = doesJwtUserHasRole(req, role);
147
173
  _isLoginRequired(isRoleExist, customErrorFunction);
148
174
  next();
@@ -152,10 +178,11 @@ function verifyJwtAndRole(role, publicKey, customErrorFunction) {
152
178
  };
153
179
  }
154
180
 
155
- function verifyJwt(publicKey, customErrorFunction) {
181
+ // Pattern: Middleware - verifies the primary JWT and optionally attaches user authorization context.
182
+ function verifyJwt(publicKey, customErrorFunction, options) {
156
183
  return function (req, res, next) {
157
184
  try {
158
- validateAndExtractJwtObject(req, publicKey, customErrorFunction);
185
+ validateAndExtractJwtObject(req, publicKey, customErrorFunction, options);
159
186
  next();
160
187
  } catch (err) {
161
188
  next(err);
@@ -163,10 +190,11 @@ function verifyJwt(publicKey, customErrorFunction) {
163
190
  };
164
191
  }
165
192
 
166
- function verifyServiceJwt(publicKey, expectedIssuer, expectedAudience, customErrorFunction) {
193
+ // Pattern: Middleware - verifies service identity and optionally carries user authorization context.
194
+ function verifyServiceJwt(publicKey, expectedIssuer, expectedAudience, customErrorFunction, options) {
167
195
  return function (req, res, next) {
168
196
  try {
169
- validateAndExtractServiceJwtObject(req, publicKey, expectedIssuer, expectedAudience, customErrorFunction);
197
+ validateAndExtractServiceJwtObject(req, publicKey, expectedIssuer, expectedAudience, customErrorFunction, options);
170
198
  next();
171
199
  } catch (err) {
172
200
  next(err);
@@ -174,10 +202,11 @@ function verifyServiceJwt(publicKey, expectedIssuer, expectedAudience, customErr
174
202
  };
175
203
  }
176
204
 
177
- function verifyJwtOrServerAuth(publicKey, serverAuthIntrospector, customErrorFunction) {
205
+ // Pattern: Middleware - accepts JWT or server-auth and then optionally verifies scoped authorization.
206
+ function verifyJwtOrServerAuth(publicKey, serverAuthIntrospector, customErrorFunction, options) {
178
207
  return async function (req, res, next) {
179
208
  try {
180
- await validateAndExtractJwtOrServerAuthObject(req, publicKey, serverAuthIntrospector, customErrorFunction);
209
+ await validateAndExtractJwtOrServerAuthObject(req, publicKey, serverAuthIntrospector, customErrorFunction, options);
181
210
  next();
182
211
  } catch (err) {
183
212
  next(err);
@@ -185,10 +214,11 @@ function verifyJwtOrServerAuth(publicKey, serverAuthIntrospector, customErrorFun
185
214
  };
186
215
  }
187
216
 
188
- function verifyJwtOrServerAuthAndHasRole(role, publicKey, serverAuthIntrospector, customErrorFunction) {
217
+ // Pattern: Middleware - composes flexible auth, optional user authorization, and role checks.
218
+ function verifyJwtOrServerAuthAndHasRole(role, publicKey, serverAuthIntrospector, customErrorFunction, options) {
189
219
  return async function (req, res, next) {
190
220
  try {
191
- await validateAndExtractJwtOrServerAuthObject(req, publicKey, serverAuthIntrospector, customErrorFunction);
221
+ await validateAndExtractJwtOrServerAuthObject(req, publicKey, serverAuthIntrospector, customErrorFunction, options);
192
222
  const isRoleExist = doesJwtUserHasRole(req, role);
193
223
  _isLoginRequired(isRoleExist, customErrorFunction);
194
224
  next();
@@ -198,10 +228,23 @@ function verifyJwtOrServerAuthAndHasRole(role, publicKey, serverAuthIntrospector
198
228
  };
199
229
  }
200
230
 
201
- function verifyWebToken(publicKey, headerName, customErrorFunction) {
231
+ // Pattern: Middleware - verifies a custom-header JWT and optionally scoped user authorization.
232
+ function verifyWebToken(publicKey, headerName, customErrorFunction, options) {
233
+ return function (req, res, next) {
234
+ try {
235
+ validateAndExtractWebToken(req, publicKey, headerName, customErrorFunction, options);
236
+ next();
237
+ } catch (err) {
238
+ next(err);
239
+ }
240
+ };
241
+ }
242
+
243
+ // Pattern: Middleware - preserves no-throw JWT behavior while clearing invalid optional context.
244
+ function verifyJwtNoThrow(publicKey, options) {
202
245
  return function (req, res, next) {
203
246
  try {
204
- validateAndExtractWebToken(req, publicKey, headerName, customErrorFunction);
247
+ validateAndExtractJwtObjectNoThrow(req, publicKey, options);
205
248
  next();
206
249
  } catch (err) {
207
250
  next(err);
@@ -209,10 +252,11 @@ function verifyWebToken(publicKey, headerName, customErrorFunction) {
209
252
  };
210
253
  }
211
254
 
212
- function verifyJwtNoThrow(publicKey) {
255
+ // Pattern: Middleware - preserves custom-header no-throw behavior with optional context extraction.
256
+ function verifyWebTokenNoThrow(publicKey, headerName, options) {
213
257
  return function (req, res, next) {
214
258
  try {
215
- validateAndExtractJwtObjectNoThrow(req, publicKey);
259
+ validateAndExtractWebTokenObjectNoThrow(req, publicKey, headerName, options);
216
260
  next();
217
261
  } catch (err) {
218
262
  next(err);
@@ -220,10 +264,11 @@ function verifyJwtNoThrow(publicKey) {
220
264
  };
221
265
  }
222
266
 
223
- function verifyWebTokenNoThrow(publicKey, headerName) {
267
+ // Pattern: Middleware - keeps visitor extraction no-throw and independently reads optional context.
268
+ function verifyVisitorNoThrow(publicKey, options) {
224
269
  return function (req, res, next) {
225
270
  try {
226
- validateAndExtractWebTokenObjectNoThrow(req, publicKey, headerName);
271
+ validateAndExtractVisitorObjectNoThrow(req, publicKey, options);
227
272
  next();
228
273
  } catch (err) {
229
274
  next(err);
@@ -231,10 +276,11 @@ function verifyWebTokenNoThrow(publicKey, headerName) {
231
276
  };
232
277
  }
233
278
 
234
- function verifyVisitorNoThrow(publicKey) {
279
+ // Pattern: Middleware - verifies only the scoped user authorization token.
280
+ function verifyUserAuthorization(publicKey, customErrorFunction, options) {
235
281
  return function (req, res, next) {
236
282
  try {
237
- validateAndExtractVisitorObjectNoThrow(req, publicKey);
283
+ validateAndExtractUserAuthorizationObject(req, publicKey, customErrorFunction, options);
238
284
  next();
239
285
  } catch (err) {
240
286
  next(err);
@@ -242,6 +288,88 @@ function verifyVisitorNoThrow(publicKey) {
242
288
  };
243
289
  }
244
290
 
291
+ // Pattern: Middleware - reads scoped user authorization without throwing for invalid tokens.
292
+ function verifyUserAuthorizationNoThrow(publicKey, options) {
293
+ return function (req, res, next) {
294
+ try {
295
+ validateAndExtractUserAuthorizationObjectNoThrow(req, publicKey, options);
296
+ next();
297
+ } catch (err) {
298
+ next(err);
299
+ }
300
+ };
301
+ }
302
+
303
+ // Pattern: Single Responsibility - validates and attaches only the scoped user authorization token.
304
+ function validateAndExtractUserAuthorizationObject(req, publicKey, customErrorFunction, options) {
305
+ const config = createUserAuthorizationConfig(publicKey, options);
306
+ const header = readUserAuthorizationHeader(req, config);
307
+
308
+ if (header.present && isUserAuthorizationTokenAllowed(header.token, config)) {
309
+ _extractUserAuthorizationObjectNoThrow(req, header.token);
310
+ } else {
311
+ if (req) req.userAuthorization = null;
312
+ throwError(customErrorFunction);
313
+ }
314
+
315
+ return req;
316
+ }
317
+
318
+ // Pattern: Single Responsibility - clears invalid scoped authorization without changing primary auth state.
319
+ function validateAndExtractUserAuthorizationObjectNoThrow(req, publicKey, options) {
320
+ const config = createUserAuthorizationConfig(publicKey, options);
321
+ const header = readUserAuthorizationHeader(req, config);
322
+
323
+ if (header.present && isUserAuthorizationTokenAllowed(header.token, config)) {
324
+ _extractUserAuthorizationObjectNoThrow(req, header.token);
325
+ } else if (req) {
326
+ req.userAuthorization = null;
327
+ }
328
+
329
+ return req;
330
+ }
331
+
332
+ // Pattern: Guard Clause - skips optional context handling unless a caller explicitly configured it.
333
+ function validateAndExtractOptionalUserAuthorizationObject(req, options, customErrorFunction) {
334
+ const config = createOptionalUserAuthorizationConfig(options);
335
+ if (!config) return req;
336
+
337
+ const header = readUserAuthorizationHeader(req, config);
338
+ if (!header.present) {
339
+ /* istanbul ignore else */
340
+ if (req) req.userAuthorization = null;
341
+ return req;
342
+ }
343
+
344
+ if (isUserAuthorizationTokenAllowed(header.token, config)) {
345
+ _extractUserAuthorizationObjectNoThrow(req, header.token);
346
+ } else {
347
+ /* istanbul ignore else */
348
+ if (req) req.userAuthorization = null;
349
+ throwError(customErrorFunction);
350
+ }
351
+
352
+ return req;
353
+ }
354
+
355
+ // Pattern: Guard Clause - optional no-throw context extraction never changes primary auth results.
356
+ function validateAndExtractOptionalUserAuthorizationObjectNoThrow(req, options) {
357
+ const config = createOptionalUserAuthorizationConfig(options);
358
+ if (!config) return req;
359
+
360
+ const header = readUserAuthorizationHeader(req, config);
361
+ if (header.present && isUserAuthorizationTokenAllowed(header.token, config)) {
362
+ _extractUserAuthorizationObjectNoThrow(req, header.token);
363
+ } else {
364
+ /* istanbul ignore else */
365
+ if (req) {
366
+ req.userAuthorization = null;
367
+ }
368
+ }
369
+
370
+ return req;
371
+ }
372
+
245
373
  function throwUsedTokenError() {
246
374
  throw new Error('Used_Token');
247
375
  }
@@ -261,6 +389,7 @@ function tryValidateAndExtractJwtObject(req, publicKey) {
261
389
 
262
390
  const isJwtSignatureValid = jwtVerifySignedToken(jwtString, publicKey);
263
391
  if (!isJwtSignatureValid) {
392
+ /* istanbul ignore else */
264
393
  if (req) req.jwt = null;
265
394
  return false;
266
395
  }
@@ -371,6 +500,91 @@ function isJwtPayloadNotYetValid(payload) {
371
500
  return Math.floor(Date.now() / 1000) < nbf;
372
501
  }
373
502
 
503
+ // Pattern: Factory - normalizes direct user authorization options into one config shape.
504
+ function createUserAuthorizationConfig(publicKey, options) {
505
+ return normalizeUserAuthorizationConfig({
506
+ ...(options || {}),
507
+ publicKey,
508
+ });
509
+ }
510
+
511
+ // Pattern: Factory - detects optional user authorization configuration without affecting legacy callers.
512
+ function createOptionalUserAuthorizationConfig(options) {
513
+ if (!options || !Object.prototype.hasOwnProperty.call(options, 'userAuthorization')) return null;
514
+ return normalizeUserAuthorizationConfig(options.userAuthorization || {});
515
+ }
516
+
517
+ // Pattern: Pure Function - centralizes defaults for scoped authorization header verification.
518
+ function normalizeUserAuthorizationConfig(options) {
519
+ return {
520
+ publicKey: options?.publicKey,
521
+ headerName: options?.headerName || DEFAULT_USER_AUTHORIZATION_HEADER_NAME,
522
+ maxTokenLength: normalizePositiveInteger(options?.maxTokenLength, DEFAULT_USER_AUTHORIZATION_MAX_TOKEN_LENGTH),
523
+ expectedType: options?.expectedType,
524
+ expectedIssuer: options?.expectedIssuer,
525
+ expectedAudience: options?.expectedAudience,
526
+ };
527
+ }
528
+
529
+ // Pattern: Pure Function - keeps numeric option validation deterministic and local.
530
+ function normalizePositiveInteger(value, defaultValue) {
531
+ const normalized = Number(value);
532
+ return Number.isInteger(normalized) && normalized > 0 ? normalized : defaultValue;
533
+ }
534
+
535
+ // Pattern: Single Responsibility - reads only the configured header and never logs token material.
536
+ function readUserAuthorizationHeader(req, config) {
537
+ const rawToken = getRequestHeader(req, config.headerName);
538
+ const present = rawToken !== undefined && rawToken !== null;
539
+ if (!present || typeof rawToken !== 'string') return { present, token: null };
540
+
541
+ const token = rawToken.trim();
542
+ if (!token || token.length > config.maxTokenLength || !isJwtString(token)) return { present: true, token: null };
543
+ return { present: true, token };
544
+ }
545
+
546
+ // Pattern: Adapter - supports Express case-insensitive headers and simple test doubles.
547
+ function getRequestHeader(req, headerName) {
548
+ const value = req?.get?.(headerName);
549
+ if (value !== undefined && value !== null) return value;
550
+
551
+ const lowerCaseHeaderName = headerName.toLowerCase();
552
+ if (lowerCaseHeaderName === headerName) return value;
553
+ return req?.get?.(lowerCaseHeaderName);
554
+ }
555
+
556
+ // Pattern: Single Responsibility - verifies signature and registered JWT time claims for user authorization.
557
+ function isUserAuthorizationTokenAllowed(token, config) {
558
+ if (!token || !config.publicKey || !jwtVerifySignedToken(token, config.publicKey)) return false;
559
+
560
+ const payload = readJwtPayloadNoThrow(token);
561
+ if (!payload) return false;
562
+ if (isJwtPayloadIssuedInFuture(payload)) return false;
563
+ if (isJwtPayloadExpired(payload)) return false;
564
+ if (isJwtPayloadNotYetValid(payload)) return false;
565
+ if (config.expectedType && payload.typ !== config.expectedType) return false;
566
+ if (config.expectedIssuer && payload.iss !== config.expectedIssuer) return false;
567
+ if (!expectedAudienceMatches(payload.aud, config.expectedAudience)) return false;
568
+
569
+ return true;
570
+ }
571
+
572
+ // Pattern: Pure Function - decodes payload defensively after signature and shape checks.
573
+ function readJwtPayloadNoThrow(token) {
574
+ try {
575
+ return jwtGetHeaderPayload(token)?.payload || null;
576
+ } catch {
577
+ return null;
578
+ }
579
+ }
580
+
581
+ // Pattern: Pure Function - supports either one expected audience or a small allowed set.
582
+ function expectedAudienceMatches(actualAudience, expectedAudience) {
583
+ if (expectedAudience === undefined || expectedAudience === null || expectedAudience === '') return true;
584
+ if (Array.isArray(expectedAudience)) return expectedAudience.some(audience => payloadAudienceMatches(actualAudience, audience));
585
+ return payloadAudienceMatches(actualAudience, expectedAudience);
586
+ }
587
+
374
588
  function _validateGenericNoThrow(req, headerName, extractor) {
375
589
  let jwtRaw = req.get(headerName);
376
590
  if (!jwtRaw) return null;
@@ -429,6 +643,10 @@ function _extractVisitorObjectNoThrow(req, jwt) {
429
643
  _extractGenericObjectNoThrow(req, jwt, _attachVisitorMethods, 'visitor');
430
644
  }
431
645
 
646
+ function _extractUserAuthorizationObjectNoThrow(req, jwt) {
647
+ _extractGenericObjectNoThrow(req, jwt, () => {}, 'userAuthorization');
648
+ }
649
+
432
650
  function _attachJwtMethods(jwtObj) {
433
651
  if (jwtObj) {
434
652
  Object.defineProperties(jwtObj, {
@@ -540,6 +758,8 @@ module.exports = {
540
758
  _isJwtSignatureValidNoThrow,
541
759
  _extractJwtObject,
542
760
  validateAndExtractJwtObject,
761
+ validateAndExtractUserAuthorizationObject,
762
+ validateAndExtractUserAuthorizationObjectNoThrow,
543
763
  validateAndExtractServiceJwtObject,
544
764
  validateAndExtractJwtOrServerAuthObject,
545
765
  validateAndExtractWebToken,
@@ -553,6 +773,8 @@ module.exports = {
553
773
  verifyJwtOrServerAuth,
554
774
  verifyJwtOrServerAuthAndHasRole,
555
775
  verifyJwt,
776
+ verifyUserAuthorization,
777
+ verifyUserAuthorizationNoThrow,
556
778
  verifyWebTokenNoThrow,
557
779
  verifyWebToken,
558
780
  verifyJwtNoThrow,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carecard/jwt-read",
3
- "version": "3.1.16",
3
+ "version": "3.1.18",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/CareCard-ca/pkg-jwt-read.git"
@@ -41,8 +41,8 @@
41
41
  "typescript": "6.0.3"
42
42
  },
43
43
  "dependencies": {
44
+ "@carecard/auth-util": "3.1.16",
44
45
  "@carecard/common-util": "3.1.15",
45
- "@carecard/auth-util": "3.1.15",
46
- "@carecard/validate": "3.1.24"
46
+ "@carecard/validate": "3.1.27"
47
47
  }
48
48
  }
package/readme.md CHANGED
@@ -18,6 +18,9 @@ introspected by `ms-auth`.
18
18
  - **Service JWTs**: Helpers for verifying and extracting microservice-to-microservice JWTs with standard `iss`, `sub`, `aud`, `iat`, and `exp` claims.
19
19
  - **JWT or Server Auth**: Middleware helpers that verify normal JWTs locally and
20
20
  call a service-provided introspector for opaque server-auth tokens.
21
+ - **Scoped User Authorization**: Optional `X-Authorization-Context`
22
+ verification attaches compact scoped authorization claims to
23
+ `req.userAuthorization` without replacing `req.jwt`.
21
24
 
22
25
  ## Installation
23
26
 
@@ -133,6 +136,44 @@ those claims onto `req.jwt.payload` with `authMode: "server-auth"` and
133
136
  `auth_mode: "server-auth"` so services can keep their existing JWT-backed
134
137
  database context and role checks.
135
138
 
139
+ ### Scoped User Authorization Context
140
+
141
+ Use `jwtVerifyUserAuthorization` when a route needs to verify only the compact
142
+ authorization-context token carried in `X-Authorization-Context`. The token is
143
+ read as a raw JWT header value, not as `Bearer <token>`.
144
+
145
+ ```javascript
146
+ const { jwtVerifyUserAuthorization } = require('@carecard/jwt-read');
147
+
148
+ app.use(
149
+ jwtVerifyUserAuthorization(institutionsPublicKey, throwNotAuthorizedError, {
150
+ expectedType: 'carecard.authorization-context.scoped.v1',
151
+ expectedIssuer: 'ms-institutions',
152
+ expectedAudience: 'ms-documents',
153
+ }),
154
+ );
155
+ ```
156
+
157
+ Existing JWT verification helpers can also read the header by passing an
158
+ optional trailing options object. This preserves current `req.jwt` behavior and
159
+ adds decoded scoped claims to `req.userAuthorization`.
160
+
161
+ ```javascript
162
+ const verifyUser = jwtVerifyOrServerAuth(msAuthPublicKey, token => introspectServerAuthTokenWithMsAuth(token), throwNotAuthorizedError, {
163
+ userAuthorization: {
164
+ publicKey: institutionsPublicKey,
165
+ expectedType: 'carecard.authorization-context.scoped.v1',
166
+ expectedIssuer: 'ms-institutions',
167
+ expectedAudience: 'ms-documents',
168
+ },
169
+ });
170
+ ```
171
+
172
+ When the optional reader is configured, a missing `X-Authorization-Context`
173
+ leaves `req.userAuthorization` as `null`. If the header is present but invalid,
174
+ throwing middleware fails closed. No-throw middleware clears
175
+ `req.userAuthorization` and continues.
176
+
136
177
  ## Testing
137
178
 
138
179
  Run tests using: