oauth4webapi 2.5.0 → 2.6.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/README.md +3 -2
- package/build/index.d.ts +51 -13
- package/build/index.js +150 -13
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@ import * as oauth2 from 'oauth4webapi'
|
|
|
43
43
|
**`example`** Deno import
|
|
44
44
|
|
|
45
45
|
```js
|
|
46
|
-
import * as oauth2 from 'https://deno.land/x/oauth4webapi@v2.
|
|
46
|
+
import * as oauth2 from 'https://deno.land/x/oauth4webapi@v2.6.0/mod.ts'
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
- Authorization Code Flow - OpenID Connect [source](examples/code.ts), or plain OAuth 2 [source](examples/oauth.ts)
|
|
@@ -53,7 +53,8 @@ import * as oauth2 from 'https://deno.land/x/oauth4webapi@v2.5.0/mod.ts'
|
|
|
53
53
|
- Pushed Authorization Request (PAR) - [source](examples/par.ts) | [diff from code flow](examples/par.diff)
|
|
54
54
|
- Client Credentials Grant - [source](examples/client_credentials.ts)
|
|
55
55
|
- Device Authorization Grant - [source](examples/device_authorization_grant.ts)
|
|
56
|
-
- FAPI
|
|
56
|
+
- FAPI 1.0 Advanced (Private Key JWT, MTLS, JAR) - [source](examples/fapi1-advanced.ts)
|
|
57
|
+
- FAPI 2.0 Security Profile (Private Key JWT, PAR, DPoP) - [source](examples/fapi2.ts)
|
|
57
58
|
- FAPI 2.0 Message Signing (Private Key JWT, PAR, DPoP, JAR, JARM) - [source](examples/fapi2-message-signing.ts) | [diff](examples/fapi2-message-signing.diff)
|
|
58
59
|
|
|
59
60
|
## Supported Runtimes
|
package/build/index.d.ts
CHANGED
|
@@ -169,7 +169,7 @@ export declare const clockTolerance: unique symbol;
|
|
|
169
169
|
*
|
|
170
170
|
* // example use
|
|
171
171
|
* await oauth.discoveryRequest(new URL('https://as.example.com'), {
|
|
172
|
-
* [oauth.
|
|
172
|
+
* [oauth.experimental_customFetch]: (...args) =>
|
|
173
173
|
* ky(args[0], {
|
|
174
174
|
* ...args[1],
|
|
175
175
|
* hooks: {
|
|
@@ -210,16 +210,20 @@ export declare const clockTolerance: unique symbol;
|
|
|
210
210
|
*
|
|
211
211
|
* // example use
|
|
212
212
|
* await oauth.discoveryRequest(new URL('https://as.example.com'), {
|
|
213
|
-
* [oauth.
|
|
213
|
+
* [oauth.experimental_customFetch]: undici.fetch,
|
|
214
214
|
* })
|
|
215
215
|
* ```
|
|
216
|
+
*
|
|
217
|
+
* @group Experimental
|
|
216
218
|
*/
|
|
217
|
-
export declare const
|
|
219
|
+
export declare const experimental_customFetch: unique symbol;
|
|
220
|
+
/** @ignore */
|
|
221
|
+
export declare const experimentalCustomFetch: symbol;
|
|
218
222
|
/**
|
|
219
223
|
* This is an experimental feature, it is not subject to semantic versioning rules. Non-backward
|
|
220
224
|
* compatible changes or removal may occur in any future release.
|
|
221
225
|
*
|
|
222
|
-
* When combined with {@link
|
|
226
|
+
* When combined with {@link experimental_customFetch} (to use a Fetch API implementation that
|
|
223
227
|
* supports client certificates) this can be used to target FAPI 2.0 profiles that utilize
|
|
224
228
|
* Mutual-TLS for either client authentication or sender constraining. FAPI 1.0 Advanced profiles
|
|
225
229
|
* that use PAR and JARM can also be targetted.
|
|
@@ -238,8 +242,8 @@ export declare const experimentalCustomFetch: unique symbol;
|
|
|
238
242
|
* import * as oauth from 'oauth4webapi'
|
|
239
243
|
*
|
|
240
244
|
* const response = await oauth.pushedAuthorizationRequest(as, client, params, {
|
|
241
|
-
* [oauth.
|
|
242
|
-
* [oauth.
|
|
245
|
+
* [oauth.experimental_useMtlsAlias]: true,
|
|
246
|
+
* [oauth.experimental_customFetch]: (...args) => {
|
|
243
247
|
* return undici.fetch(args[0], {
|
|
244
248
|
* ...args[1],
|
|
245
249
|
* dispatcher: new undici.Agent({
|
|
@@ -268,8 +272,8 @@ export declare const experimentalCustomFetch: unique symbol;
|
|
|
268
272
|
* })
|
|
269
273
|
*
|
|
270
274
|
* const response = await oauth.pushedAuthorizationRequest(as, client, params, {
|
|
271
|
-
* [oauth.
|
|
272
|
-
* [oauth.
|
|
275
|
+
* [oauth.experimental_useMtlsAlias]: true,
|
|
276
|
+
* [oauth.experimental_customFetch]: (...args) => {
|
|
273
277
|
* return fetch(args[0], {
|
|
274
278
|
* ...args[1],
|
|
275
279
|
* client: agent,
|
|
@@ -278,9 +282,13 @@ export declare const experimentalCustomFetch: unique symbol;
|
|
|
278
282
|
* })
|
|
279
283
|
* ```
|
|
280
284
|
*
|
|
285
|
+
* @group Experimental
|
|
286
|
+
*
|
|
281
287
|
* @see [RFC 8705 - OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens](https://www.rfc-editor.org/rfc/rfc8705.html)
|
|
282
288
|
*/
|
|
283
|
-
export declare const
|
|
289
|
+
export declare const experimental_useMtlsAlias: unique symbol;
|
|
290
|
+
/** @ignore */
|
|
291
|
+
export declare const experimentalUseMtlsAlias: symbol;
|
|
284
292
|
/**
|
|
285
293
|
* Authorization Server Metadata
|
|
286
294
|
*
|
|
@@ -680,9 +688,11 @@ export interface HttpRequestOptions {
|
|
|
680
688
|
* This is an experimental feature, it is not subject to semantic versioning rules. Non-backward
|
|
681
689
|
* compatible changes or removal may occur in any future release.
|
|
682
690
|
*
|
|
683
|
-
* See {@link
|
|
691
|
+
* See {@link experimental_customFetch} for its documentation.
|
|
692
|
+
*
|
|
693
|
+
* @group Experimental
|
|
684
694
|
*/
|
|
685
|
-
[
|
|
695
|
+
[experimental_customFetch]?: typeof fetch;
|
|
686
696
|
}
|
|
687
697
|
export interface DiscoveryRequestOptions extends HttpRequestOptions {
|
|
688
698
|
/** The issuer transformation algorithm to use. */
|
|
@@ -786,9 +796,11 @@ export interface ExperimentalUseMTLSAliasOptions {
|
|
|
786
796
|
* This is an experimental feature, it is not subject to semantic versioning rules. Non-backward
|
|
787
797
|
* compatible changes or removal may occur in any future release.
|
|
788
798
|
*
|
|
789
|
-
* See {@link
|
|
799
|
+
* See {@link experimental_useMtlsAlias} for its documentation.
|
|
800
|
+
*
|
|
801
|
+
* @group Experimental
|
|
790
802
|
*/
|
|
791
|
-
[
|
|
803
|
+
[experimental_useMtlsAlias]?: boolean;
|
|
792
804
|
}
|
|
793
805
|
export interface AuthenticatedRequestOptions extends ExperimentalUseMTLSAliasOptions {
|
|
794
806
|
/**
|
|
@@ -1349,6 +1361,32 @@ export declare function processIntrospectionResponse(as: AuthorizationServer, cl
|
|
|
1349
1361
|
* @see [JWT Secured Authorization Response Mode for OAuth 2.0 (JARM)](https://openid.net/specs/openid-financial-api-jarm.html)
|
|
1350
1362
|
*/
|
|
1351
1363
|
export declare function validateJwtAuthResponse(as: AuthorizationServer, client: Client, parameters: URLSearchParams | URL, expectedState?: string | typeof expectNoState | typeof skipStateCheck, options?: HttpRequestOptions): Promise<URLSearchParams | OAuth2Error>;
|
|
1364
|
+
/**
|
|
1365
|
+
* This is an experimental feature, it is not subject to semantic versioning rules. Non-backward
|
|
1366
|
+
* compatible changes or removal may occur in any future release.
|
|
1367
|
+
*
|
|
1368
|
+
* Same as {@link validateAuthResponse} but for FAPI 1.0 Advanced Detached Signature authorization
|
|
1369
|
+
* responses.
|
|
1370
|
+
*
|
|
1371
|
+
* @param as Authorization Server Metadata.
|
|
1372
|
+
* @param client Client Metadata.
|
|
1373
|
+
* @param parameters Authorization Response.
|
|
1374
|
+
* @param expectedNonce Expected ID Token `nonce` claim value.
|
|
1375
|
+
* @param expectedState Expected `state` parameter value. Default is {@link expectNoState}.
|
|
1376
|
+
* @param maxAge ID Token {@link IDToken.auth_time `auth_time`} claim value will be checked to be
|
|
1377
|
+
* present and conform to the `maxAge` value. Use of this option is required if you sent a
|
|
1378
|
+
* `max_age` parameter in an authorization request. Default is
|
|
1379
|
+
* {@link Client.default_max_age `client.default_max_age`} and falls back to
|
|
1380
|
+
* {@link skipAuthTimeCheck}.
|
|
1381
|
+
*
|
|
1382
|
+
* @returns Validated Authorization Response parameters or Authorization Error Response.
|
|
1383
|
+
*
|
|
1384
|
+
* @group FAPI 1.0 Advanced
|
|
1385
|
+
* @group Experimental
|
|
1386
|
+
*
|
|
1387
|
+
* @see [Financial-grade API Security Profile 1.0 - Part 2: Advanced](https://openid.net/specs/openid-financial-api-part-2-1_0.html#id-token-as-detached-signature)
|
|
1388
|
+
*/
|
|
1389
|
+
export declare function experimental_validateDetachedSignatureResponse(as: AuthorizationServer, client: Client, parameters: URLSearchParams, expectedNonce: string, expectedState?: string | typeof expectNoState, maxAge?: number | typeof skipAuthTimeCheck, options?: HttpRequestOptions): Promise<URLSearchParams | OAuth2Error>;
|
|
1352
1390
|
/**
|
|
1353
1391
|
* DANGER ZONE
|
|
1354
1392
|
*
|
package/build/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
let USER_AGENT;
|
|
2
2
|
if (typeof navigator === 'undefined' || !navigator.userAgent?.startsWith?.('Mozilla/5.0 ')) {
|
|
3
3
|
const NAME = 'oauth4webapi';
|
|
4
|
-
const VERSION = 'v2.
|
|
4
|
+
const VERSION = 'v2.6.0';
|
|
5
5
|
USER_AGENT = `${NAME}/${VERSION}`;
|
|
6
6
|
}
|
|
7
7
|
function looseInstanceOf(input, expected) {
|
|
@@ -18,8 +18,10 @@ function looseInstanceOf(input, expected) {
|
|
|
18
18
|
}
|
|
19
19
|
export const clockSkew = Symbol();
|
|
20
20
|
export const clockTolerance = Symbol();
|
|
21
|
-
export const
|
|
22
|
-
export const
|
|
21
|
+
export const experimental_customFetch = Symbol();
|
|
22
|
+
export const experimentalCustomFetch = experimental_customFetch;
|
|
23
|
+
export const experimental_useMtlsAlias = Symbol();
|
|
24
|
+
export const experimentalUseMtlsAlias = experimental_useMtlsAlias;
|
|
23
25
|
const encoder = new TextEncoder();
|
|
24
26
|
const decoder = new TextDecoder();
|
|
25
27
|
function buf(input) {
|
|
@@ -212,7 +214,7 @@ export async function discoveryRequest(issuerIdentifier, options) {
|
|
|
212
214
|
}
|
|
213
215
|
const headers = prepareHeaders(options?.headers);
|
|
214
216
|
headers.set('accept', 'application/json');
|
|
215
|
-
return (options?.[
|
|
217
|
+
return (options?.[experimental_customFetch] || fetch)(url.href, {
|
|
216
218
|
headers: Object.fromEntries(headers.entries()),
|
|
217
219
|
method: 'GET',
|
|
218
220
|
redirect: 'manual',
|
|
@@ -548,7 +550,7 @@ async function publicJwk(key) {
|
|
|
548
550
|
}
|
|
549
551
|
function validateEndpoint(value, endpoint, options) {
|
|
550
552
|
if (typeof value !== 'string') {
|
|
551
|
-
if (options?.[
|
|
553
|
+
if (options?.[experimental_useMtlsAlias]) {
|
|
552
554
|
throw new TypeError(`"as.mtls_endpoint_aliases.${endpoint}" must be a string`);
|
|
553
555
|
}
|
|
554
556
|
else {
|
|
@@ -558,7 +560,7 @@ function validateEndpoint(value, endpoint, options) {
|
|
|
558
560
|
return new URL(value);
|
|
559
561
|
}
|
|
560
562
|
function resolveEndpoint(as, endpoint, options) {
|
|
561
|
-
if (options?.[
|
|
563
|
+
if (options?.[experimental_useMtlsAlias] &&
|
|
562
564
|
as.mtls_endpoint_aliases &&
|
|
563
565
|
endpoint in as.mtls_endpoint_aliases) {
|
|
564
566
|
return validateEndpoint(as.mtls_endpoint_aliases[endpoint], endpoint, options);
|
|
@@ -690,7 +692,7 @@ export async function protectedResourceRequest(accessToken, method, url, headers
|
|
|
690
692
|
await dpopProofJwt(headers, options.DPoP, url, 'GET', getClockSkew({ [clockSkew]: options?.[clockSkew] }), accessToken);
|
|
691
693
|
headers.set('authorization', `DPoP ${accessToken}`);
|
|
692
694
|
}
|
|
693
|
-
return (options?.[
|
|
695
|
+
return (options?.[experimental_customFetch] || fetch)(url.href, {
|
|
694
696
|
body,
|
|
695
697
|
headers: Object.fromEntries(headers.entries()),
|
|
696
698
|
method,
|
|
@@ -853,7 +855,7 @@ export async function processUserInfoResponse(as, client, expectedSubject, respo
|
|
|
853
855
|
async function authenticatedRequest(as, client, method, url, body, headers, options) {
|
|
854
856
|
await clientAuthentication(as, client, body, headers, options?.clientPrivateKey);
|
|
855
857
|
headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8');
|
|
856
|
-
return (options?.[
|
|
858
|
+
return (options?.[experimental_customFetch] || fetch)(url.href, {
|
|
857
859
|
body,
|
|
858
860
|
headers: Object.fromEntries(headers.entries()),
|
|
859
861
|
method,
|
|
@@ -1017,17 +1019,20 @@ export async function authorizationCodeGrantRequest(as, client, callbackParamete
|
|
|
1017
1019
|
parameters.set('code', code);
|
|
1018
1020
|
return tokenEndpointRequest(as, client, 'authorization_code', parameters, options);
|
|
1019
1021
|
}
|
|
1020
|
-
const
|
|
1022
|
+
const idTokenClaimNames = {
|
|
1021
1023
|
aud: 'audience',
|
|
1022
1024
|
exp: 'expiration time',
|
|
1023
1025
|
iat: 'issued at',
|
|
1024
1026
|
iss: 'issuer',
|
|
1025
1027
|
sub: 'subject',
|
|
1028
|
+
nonce: 'nonce',
|
|
1029
|
+
s_hash: 'state hash',
|
|
1030
|
+
c_hash: 'code hash',
|
|
1026
1031
|
};
|
|
1027
1032
|
function validatePresence(required, result) {
|
|
1028
1033
|
for (const claim of required) {
|
|
1029
1034
|
if (result.claims[claim] === undefined) {
|
|
1030
|
-
throw new OPE(`JWT "${claim}" (${
|
|
1035
|
+
throw new OPE(`JWT "${claim}" (${idTokenClaimNames[claim]}) claim missing`);
|
|
1031
1036
|
}
|
|
1032
1037
|
}
|
|
1033
1038
|
return result;
|
|
@@ -1207,7 +1212,7 @@ async function jwksRequest(as, options) {
|
|
|
1207
1212
|
const headers = prepareHeaders(options?.headers);
|
|
1208
1213
|
headers.set('accept', 'application/json');
|
|
1209
1214
|
headers.append('accept', 'application/jwk-set+json');
|
|
1210
|
-
return (options?.[
|
|
1215
|
+
return (options?.[experimental_customFetch] || fetch)(url.href, {
|
|
1211
1216
|
headers: Object.fromEntries(headers.entries()),
|
|
1212
1217
|
method: 'GET',
|
|
1213
1218
|
redirect: 'manual',
|
|
@@ -1342,8 +1347,9 @@ async function validateJwt(jws, checkAlg, getKey, clockSkew, clockTolerance) {
|
|
|
1342
1347
|
throw new OPE('unexpected JWT "crit" header parameter');
|
|
1343
1348
|
}
|
|
1344
1349
|
const signature = b64u(encodedSignature);
|
|
1350
|
+
let key;
|
|
1345
1351
|
if (getKey !== noSignatureCheck) {
|
|
1346
|
-
|
|
1352
|
+
key = await getKey(header);
|
|
1347
1353
|
const input = `${protectedHeader}.${payload}`;
|
|
1348
1354
|
const verified = await crypto.subtle.verify(keyToSubtle(key), key, signature, buf(input));
|
|
1349
1355
|
if (!verified) {
|
|
@@ -1392,7 +1398,7 @@ async function validateJwt(jws, checkAlg, getKey, clockSkew, clockTolerance) {
|
|
|
1392
1398
|
throw new OPE('unexpected JWT "aud" (audience) claim type');
|
|
1393
1399
|
}
|
|
1394
1400
|
}
|
|
1395
|
-
return { header, claims, signature };
|
|
1401
|
+
return { header, claims, signature, key };
|
|
1396
1402
|
}
|
|
1397
1403
|
export async function validateJwtAuthResponse(as, client, parameters, expectedState, options) {
|
|
1398
1404
|
assertAs(as);
|
|
@@ -1422,6 +1428,137 @@ export async function validateJwtAuthResponse(as, client, parameters, expectedSt
|
|
|
1422
1428
|
}
|
|
1423
1429
|
return validateAuthResponse(as, client, result, expectedState);
|
|
1424
1430
|
}
|
|
1431
|
+
async function idTokenHash(alg, data, key) {
|
|
1432
|
+
let algorithm;
|
|
1433
|
+
switch (alg) {
|
|
1434
|
+
case 'RS256':
|
|
1435
|
+
case 'PS256':
|
|
1436
|
+
case 'ES256':
|
|
1437
|
+
algorithm = 'SHA-256';
|
|
1438
|
+
break;
|
|
1439
|
+
case 'RS384':
|
|
1440
|
+
case 'PS384':
|
|
1441
|
+
case 'ES384':
|
|
1442
|
+
algorithm = 'SHA-384';
|
|
1443
|
+
break;
|
|
1444
|
+
case 'RS512':
|
|
1445
|
+
case 'PS512':
|
|
1446
|
+
case 'ES512':
|
|
1447
|
+
algorithm = 'SHA-512';
|
|
1448
|
+
break;
|
|
1449
|
+
case 'EdDSA':
|
|
1450
|
+
if (key.algorithm.name === 'Ed25519') {
|
|
1451
|
+
algorithm = 'SHA-512';
|
|
1452
|
+
break;
|
|
1453
|
+
}
|
|
1454
|
+
throw new UnsupportedOperationError();
|
|
1455
|
+
default:
|
|
1456
|
+
throw new UnsupportedOperationError();
|
|
1457
|
+
}
|
|
1458
|
+
const digest = await crypto.subtle.digest(algorithm, buf(data));
|
|
1459
|
+
return b64u(digest.slice(0, digest.byteLength / 2));
|
|
1460
|
+
}
|
|
1461
|
+
async function idTokenHashMatches(data, actual, alg, key) {
|
|
1462
|
+
const expected = await idTokenHash(alg, data, key);
|
|
1463
|
+
return actual === expected;
|
|
1464
|
+
}
|
|
1465
|
+
export async function experimental_validateDetachedSignatureResponse(as, client, parameters, expectedNonce, expectedState, maxAge, options) {
|
|
1466
|
+
assertAs(as);
|
|
1467
|
+
assertClient(client);
|
|
1468
|
+
if (!(parameters instanceof URLSearchParams)) {
|
|
1469
|
+
throw new TypeError('"parameters" must be an instance of URLSearchParams');
|
|
1470
|
+
}
|
|
1471
|
+
parameters = new URLSearchParams(parameters);
|
|
1472
|
+
const id_token = getURLSearchParameter(parameters, 'id_token');
|
|
1473
|
+
parameters.delete('id_token');
|
|
1474
|
+
switch (expectedState) {
|
|
1475
|
+
case undefined:
|
|
1476
|
+
case expectNoState:
|
|
1477
|
+
break;
|
|
1478
|
+
default:
|
|
1479
|
+
if (!validateString(expectedState)) {
|
|
1480
|
+
throw new TypeError('"expectedState" must be a non-empty string');
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
const result = validateAuthResponse({
|
|
1484
|
+
...as,
|
|
1485
|
+
authorization_response_iss_parameter_supported: false,
|
|
1486
|
+
}, client, parameters, expectedState);
|
|
1487
|
+
if (isOAuth2Error(result)) {
|
|
1488
|
+
return result;
|
|
1489
|
+
}
|
|
1490
|
+
if (!id_token) {
|
|
1491
|
+
throw new OPE('"parameters" does not contain an ID Token');
|
|
1492
|
+
}
|
|
1493
|
+
const code = getURLSearchParameter(parameters, 'code');
|
|
1494
|
+
if (!code) {
|
|
1495
|
+
throw new OPE('"parameters" does not contain an Authorization Code');
|
|
1496
|
+
}
|
|
1497
|
+
if (typeof as.jwks_uri !== 'string') {
|
|
1498
|
+
throw new TypeError('"as.jwks_uri" must be a string');
|
|
1499
|
+
}
|
|
1500
|
+
const requiredClaims = [
|
|
1501
|
+
'aud',
|
|
1502
|
+
'exp',
|
|
1503
|
+
'iat',
|
|
1504
|
+
'iss',
|
|
1505
|
+
'sub',
|
|
1506
|
+
'nonce',
|
|
1507
|
+
'c_hash',
|
|
1508
|
+
];
|
|
1509
|
+
if (typeof expectedState === 'string') {
|
|
1510
|
+
requiredClaims.push('s_hash');
|
|
1511
|
+
}
|
|
1512
|
+
const { claims, header, key } = await validateJwt(id_token, checkSigningAlgorithm.bind(undefined, client.id_token_signed_response_alg, as.id_token_signing_alg_values_supported), getPublicSigKeyFromIssuerJwksUri.bind(undefined, as, options), getClockSkew(client), getClockTolerance(client))
|
|
1513
|
+
.then(validatePresence.bind(undefined, requiredClaims))
|
|
1514
|
+
.then(validateIssuer.bind(undefined, as.issuer))
|
|
1515
|
+
.then(validateAudience.bind(undefined, client.client_id));
|
|
1516
|
+
const clockSkew = getClockSkew(client);
|
|
1517
|
+
const now = epochTime() + clockSkew;
|
|
1518
|
+
if (claims.iat < now - 3600) {
|
|
1519
|
+
throw new OPE('unexpected JWT "iat" (issued at) claim value, it is too far in the past');
|
|
1520
|
+
}
|
|
1521
|
+
if (typeof claims.c_hash !== 'string' ||
|
|
1522
|
+
(await idTokenHashMatches(code, claims.c_hash, header.alg, key)) !== true) {
|
|
1523
|
+
throw new OPE('invalid ID Token "c_hash" (code hash) claim value');
|
|
1524
|
+
}
|
|
1525
|
+
if (claims.s_hash !== undefined && typeof expectedState !== 'string') {
|
|
1526
|
+
throw new OPE('could not verify ID Token "s_hash" (state hash) claim value');
|
|
1527
|
+
}
|
|
1528
|
+
if (typeof expectedState === 'string' &&
|
|
1529
|
+
(typeof claims.s_hash !== 'string' ||
|
|
1530
|
+
(await idTokenHashMatches(expectedState, claims.s_hash, header.alg, key)) !== true)) {
|
|
1531
|
+
throw new OPE('invalid ID Token "s_hash" (state hash) claim value');
|
|
1532
|
+
}
|
|
1533
|
+
if (client.require_auth_time !== undefined && typeof claims.auth_time !== 'number') {
|
|
1534
|
+
throw new OPE('unexpected ID Token "auth_time" (authentication time) claim value');
|
|
1535
|
+
}
|
|
1536
|
+
maxAge ?? (maxAge = client.default_max_age ?? skipAuthTimeCheck);
|
|
1537
|
+
if ((client.require_auth_time || maxAge !== skipAuthTimeCheck) &&
|
|
1538
|
+
claims.auth_time === undefined) {
|
|
1539
|
+
throw new OPE('ID Token "auth_time" (authentication time) claim missing');
|
|
1540
|
+
}
|
|
1541
|
+
if (maxAge !== skipAuthTimeCheck) {
|
|
1542
|
+
if (typeof maxAge !== 'number' || maxAge < 0) {
|
|
1543
|
+
throw new TypeError('"options.max_age" must be a non-negative number');
|
|
1544
|
+
}
|
|
1545
|
+
const now = epochTime() + getClockSkew(client);
|
|
1546
|
+
const tolerance = getClockTolerance(client);
|
|
1547
|
+
if (claims.auth_time + maxAge < now - tolerance) {
|
|
1548
|
+
throw new OPE('too much time has elapsed since the last End-User authentication');
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
if (!validateString(expectedNonce)) {
|
|
1552
|
+
throw new TypeError('"expectedNonce" must be a non-empty string');
|
|
1553
|
+
}
|
|
1554
|
+
if (claims.nonce !== expectedNonce) {
|
|
1555
|
+
throw new OPE('unexpected ID Token "nonce" claim value');
|
|
1556
|
+
}
|
|
1557
|
+
if (Array.isArray(claims.aud) && claims.aud.length !== 1 && claims.azp !== client.client_id) {
|
|
1558
|
+
throw new OPE('unexpected ID Token "azp" (authorized party) claim value');
|
|
1559
|
+
}
|
|
1560
|
+
return result;
|
|
1561
|
+
}
|
|
1425
1562
|
function checkSigningAlgorithm(client, issuer, header) {
|
|
1426
1563
|
if (client !== undefined) {
|
|
1427
1564
|
if (header.alg !== client) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oauth4webapi",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "OAuth 2 / OpenID Connect for JavaScript Runtimes",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"auth",
|
|
@@ -68,6 +68,7 @@
|
|
|
68
68
|
"@types/node": "^20.10.8",
|
|
69
69
|
"@types/oidc-provider": "^8.4.3",
|
|
70
70
|
"@types/qunit": "^2.19.9",
|
|
71
|
+
"archiver": "^6.0.1",
|
|
71
72
|
"ava": "^5.3.1",
|
|
72
73
|
"chrome-launcher": "^1.1.0",
|
|
73
74
|
"edge-runtime": "^2.5.7",
|