oauth4webapi 2.14.0 → 2.16.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 +177 -26
- package/build/index.js +59 -36
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -60,8 +60,9 @@ import * as oauth from 'oauth4webapi'
|
|
|
60
60
|
- Device Authorization Grant - [source](examples/device_authorization_grant.ts)
|
|
61
61
|
- Refresh Token Grant - [source](examples/refresh_token.ts) | [diff](examples/refresh_token.diff)
|
|
62
62
|
- FAPI
|
|
63
|
-
- FAPI 1.0 Advanced
|
|
64
|
-
- FAPI 2.0 Security Profile
|
|
63
|
+
- FAPI 1.0 Advanced - [source](examples/fapi1-advanced.ts) | [diff](examples/fapi1-advanced.diff)
|
|
64
|
+
- FAPI 2.0 Security Profile - [source](examples/fapi2.ts) | [diff](examples/fapi2.diff)
|
|
65
|
+
- FAPI 2.0 Message Signing - [source](examples/fapi2-message-signing.ts) | [diff](examples/fapi2-message-signing.diff)
|
|
65
66
|
|
|
66
67
|
|
|
67
68
|
## Supported Runtimes
|
package/build/index.d.ts
CHANGED
|
@@ -16,6 +16,17 @@ export type JsonPrimitive = string | number | boolean | null;
|
|
|
16
16
|
* JSON Values
|
|
17
17
|
*/
|
|
18
18
|
export type JsonValue = JsonPrimitive | JsonObject | JsonArray;
|
|
19
|
+
export interface ModifyAssertionFunction {
|
|
20
|
+
(
|
|
21
|
+
/**
|
|
22
|
+
* JWS Header to modify right before it is signed.
|
|
23
|
+
*/
|
|
24
|
+
header: Record<string, JsonValue | undefined>,
|
|
25
|
+
/**
|
|
26
|
+
* JWT Claims Set to modify right before it is signed.
|
|
27
|
+
*/
|
|
28
|
+
payload: Record<string, JsonValue | undefined>): void;
|
|
29
|
+
}
|
|
19
30
|
/**
|
|
20
31
|
* Interface to pass an asymmetric private key and, optionally, its associated JWK Key ID to be
|
|
21
32
|
* added as a `kid` JOSE Header Parameter.
|
|
@@ -32,6 +43,12 @@ export interface PrivateKey {
|
|
|
32
43
|
* ID) will be added to the JOSE Header.
|
|
33
44
|
*/
|
|
34
45
|
kid?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Use to modify the JWT signed by this key right before it is signed.
|
|
48
|
+
*
|
|
49
|
+
* @see {@link modifyAssertion}
|
|
50
|
+
*/
|
|
51
|
+
[modifyAssertion]?: ModifyAssertionFunction;
|
|
35
52
|
}
|
|
36
53
|
/**
|
|
37
54
|
* Supported Client Authentication Methods.
|
|
@@ -278,6 +295,100 @@ export declare const clockTolerance: unique symbol;
|
|
|
278
295
|
* ```
|
|
279
296
|
*/
|
|
280
297
|
export declare const customFetch: unique symbol;
|
|
298
|
+
/**
|
|
299
|
+
* Use to mutate JWT header and payload before they are signed. Its intended use is working around
|
|
300
|
+
* non-conform server behaviours, such as modifying JWT "aud" (audience) claims, or otherwise
|
|
301
|
+
* changing fixed claims used by this library.
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
*
|
|
305
|
+
* Changing Private Key JWT client assertion audience issued from an array to a string
|
|
306
|
+
*
|
|
307
|
+
* ```ts
|
|
308
|
+
* import * as oauth from 'oauth4webapi'
|
|
309
|
+
*
|
|
310
|
+
* // Prerequisites
|
|
311
|
+
* let as!: oauth.AuthorizationServer
|
|
312
|
+
* let client!: oauth.Client
|
|
313
|
+
* let parameters!: URLSearchParams
|
|
314
|
+
* let clientPrivateKey!: CryptoKey
|
|
315
|
+
*
|
|
316
|
+
* const response = await oauth.pushedAuthorizationRequest(as, client, parameters, {
|
|
317
|
+
* clientPrivateKey: {
|
|
318
|
+
* key: clientPrivateKey,
|
|
319
|
+
* [oauth.modifyAssertion](header, payload) {
|
|
320
|
+
* payload.aud = as.issuer
|
|
321
|
+
* },
|
|
322
|
+
* },
|
|
323
|
+
* })
|
|
324
|
+
* ```
|
|
325
|
+
*
|
|
326
|
+
* @example
|
|
327
|
+
*
|
|
328
|
+
* Changing Request Object issued by {@link issueRequestObject} to have an expiration of 5 minutes
|
|
329
|
+
*
|
|
330
|
+
* ```ts
|
|
331
|
+
* import * as oauth from 'oauth4webapi'
|
|
332
|
+
*
|
|
333
|
+
* // Prerequisites
|
|
334
|
+
* let as!: oauth.AuthorizationServer
|
|
335
|
+
* let client!: oauth.Client
|
|
336
|
+
* let parameters!: URLSearchParams
|
|
337
|
+
* let jarPrivateKey!: CryptoKey
|
|
338
|
+
*
|
|
339
|
+
* const request = await oauth.issueRequestObject(as, client, parameters, {
|
|
340
|
+
* key: jarPrivateKey,
|
|
341
|
+
* [oauth.modifyAssertion](header, payload) {
|
|
342
|
+
* payload.exp = <number>payload.iat + 300
|
|
343
|
+
* },
|
|
344
|
+
* })
|
|
345
|
+
* ```
|
|
346
|
+
*/
|
|
347
|
+
export declare const modifyAssertion: unique symbol;
|
|
348
|
+
/**
|
|
349
|
+
* Use to add support for decrypting JWEs the client encounters, namely
|
|
350
|
+
*
|
|
351
|
+
* - Encrypted ID Tokens returned by the Token Endpoint
|
|
352
|
+
* - Encrypted ID Tokens returned as part of FAPI 1.0 Advanced Detached Signature authorization
|
|
353
|
+
* responses
|
|
354
|
+
* - Encrypted JWT UserInfo responses
|
|
355
|
+
* - Encrypted JWT Introspection responses
|
|
356
|
+
* - Encrypted JARM Responses
|
|
357
|
+
*
|
|
358
|
+
* @example
|
|
359
|
+
*
|
|
360
|
+
* Decrypting JARM responses
|
|
361
|
+
*
|
|
362
|
+
* ```ts
|
|
363
|
+
* import * as oauth from 'oauth4webapi'
|
|
364
|
+
* import * as jose from 'jose'
|
|
365
|
+
*
|
|
366
|
+
* // Prerequisites
|
|
367
|
+
* let as!: oauth.AuthorizationServer
|
|
368
|
+
* let key!: CryptoKey
|
|
369
|
+
* let alg!: string
|
|
370
|
+
* let enc!: string
|
|
371
|
+
*
|
|
372
|
+
* const decoder = new TextDecoder()
|
|
373
|
+
*
|
|
374
|
+
* const client: oauth.Client = {
|
|
375
|
+
* client_id: 'urn:example:client_id',
|
|
376
|
+
* async [oauth.jweDecrypt](jwe) {
|
|
377
|
+
* const { plaintext } = await compactDecrypt(jwe, key, {
|
|
378
|
+
* keyManagementAlgorithms: [alg],
|
|
379
|
+
* contentEncryptionAlgorithms: [enc],
|
|
380
|
+
* }).catch((cause) => {
|
|
381
|
+
* throw new oauth.OperationProcessingError('decryption failed', { cause })
|
|
382
|
+
* })
|
|
383
|
+
*
|
|
384
|
+
* return decoder.decode(plaintext)
|
|
385
|
+
* },
|
|
386
|
+
* }
|
|
387
|
+
*
|
|
388
|
+
* const params = await oauth.validateJwtAuthResponse(as, client, currentUrl)
|
|
389
|
+
* ```
|
|
390
|
+
*/
|
|
391
|
+
export declare const jweDecrypt: unique symbol;
|
|
281
392
|
/**
|
|
282
393
|
* DANGER ZONE - This option has security implications that must be understood, assessed for
|
|
283
394
|
* applicability, and accepted before use. It is critical that the JSON Web Key Set cache only be
|
|
@@ -347,48 +458,45 @@ export declare const jwksCache: unique symbol;
|
|
|
347
458
|
* (Node.js) Using [nodejs/undici](https://github.com/nodejs/undici) for Mutual-TLS Client
|
|
348
459
|
* Authentication and Certificate-Bound Access Tokens support.
|
|
349
460
|
*
|
|
350
|
-
* ```
|
|
461
|
+
* ```ts
|
|
351
462
|
* import * as undici from 'undici'
|
|
352
463
|
* import * as oauth from 'oauth4webapi'
|
|
353
464
|
*
|
|
465
|
+
* // Prerequisites
|
|
466
|
+
* let as!: oauth.AuthorizationServer
|
|
467
|
+
* let client!: oauth.Client
|
|
468
|
+
* let params!: URLSearchParams
|
|
469
|
+
* let key!: string // PEM-encoded key
|
|
470
|
+
* let cert!: string // PEM-encoded certificate
|
|
471
|
+
*
|
|
472
|
+
* const agent = new undici.Agent({ connect: { key, cert } })
|
|
473
|
+
*
|
|
354
474
|
* const response = await oauth.pushedAuthorizationRequest(as, client, params, {
|
|
355
475
|
* [oauth.useMtlsAlias]: true,
|
|
356
|
-
* [oauth.customFetch]: (...args) => {
|
|
357
|
-
* return undici.fetch(args[0], {
|
|
358
|
-
* ...args[1],
|
|
359
|
-
* dispatcher: new undici.Agent({
|
|
360
|
-
* connect: {
|
|
361
|
-
* key: clientKey,
|
|
362
|
-
* cert: clientCertificate,
|
|
363
|
-
* },
|
|
364
|
-
* }),
|
|
365
|
-
* })
|
|
366
|
-
* },
|
|
476
|
+
* [oauth.customFetch]: (...args) => undici.fetch(args[0], { ...args[1], dispatcher: agent }),
|
|
367
477
|
* })
|
|
368
478
|
* ```
|
|
369
479
|
*
|
|
370
480
|
* @example
|
|
371
481
|
*
|
|
372
482
|
* (Deno) Using Deno.createHttpClient API for Mutual-TLS Client Authentication and Certificate-Bound
|
|
373
|
-
* Access Tokens support.
|
|
374
|
-
* flag.
|
|
483
|
+
* Access Tokens support.
|
|
375
484
|
*
|
|
376
|
-
* ```
|
|
485
|
+
* ```ts
|
|
377
486
|
* import * as oauth from 'oauth4webapi'
|
|
378
487
|
*
|
|
379
|
-
*
|
|
380
|
-
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
488
|
+
* // Prerequisites
|
|
489
|
+
* let as!: oauth.AuthorizationServer
|
|
490
|
+
* let client!: oauth.Client
|
|
491
|
+
* let params!: URLSearchParams
|
|
492
|
+
* let key!: string // PEM-encoded key
|
|
493
|
+
* let cert!: string // PEM-encoded certificate
|
|
494
|
+
*
|
|
495
|
+
* const agent = Deno.createHttpClient({ key, cert })
|
|
383
496
|
*
|
|
384
497
|
* const response = await oauth.pushedAuthorizationRequest(as, client, params, {
|
|
385
498
|
* [oauth.useMtlsAlias]: true,
|
|
386
|
-
* [oauth.customFetch]: (...args) => {
|
|
387
|
-
* return fetch(args[0], {
|
|
388
|
-
* ...args[1],
|
|
389
|
-
* client: agent,
|
|
390
|
-
* })
|
|
391
|
-
* },
|
|
499
|
+
* [oauth.customFetch]: (...args) => fetch(args[0], { ...args[1], client: agent }),
|
|
392
500
|
* })
|
|
393
501
|
* ```
|
|
394
502
|
*
|
|
@@ -782,6 +890,10 @@ export interface Client {
|
|
|
782
890
|
* See {@link clockTolerance}.
|
|
783
891
|
*/
|
|
784
892
|
[clockTolerance]?: number;
|
|
893
|
+
/**
|
|
894
|
+
* See {@link jweDecrypt}.
|
|
895
|
+
*/
|
|
896
|
+
[jweDecrypt]?: JweDecryptFunction;
|
|
785
897
|
[metadata: string]: JsonValue | undefined;
|
|
786
898
|
}
|
|
787
899
|
/**
|
|
@@ -923,6 +1035,12 @@ export interface DPoPOptions extends CryptoKeyPair {
|
|
|
923
1035
|
* will be used automatically.
|
|
924
1036
|
*/
|
|
925
1037
|
nonce?: string;
|
|
1038
|
+
/**
|
|
1039
|
+
* Use to modify the DPoP Proof JWT right before it is signed.
|
|
1040
|
+
*
|
|
1041
|
+
* @see {@link modifyAssertion}
|
|
1042
|
+
*/
|
|
1043
|
+
[modifyAssertion]?: ModifyAssertionFunction;
|
|
926
1044
|
}
|
|
927
1045
|
export interface DPoPRequestOptions {
|
|
928
1046
|
/**
|
|
@@ -999,6 +1117,9 @@ export interface OAuth2Error {
|
|
|
999
1117
|
* @group Token Revocation
|
|
1000
1118
|
* @group Refreshing an Access Token
|
|
1001
1119
|
* @group Pushed Authorization Requests (PAR)
|
|
1120
|
+
* @group JWT Bearer Token Grant Type
|
|
1121
|
+
* @group SAML 2.0 Bearer Assertion Grant Type
|
|
1122
|
+
* @group Token Exchange Grant Type
|
|
1002
1123
|
*/
|
|
1003
1124
|
export declare function isOAuth2Error(input?: TokenEndpointResponse | OAuth2TokenEndpointResponse | OpenIDTokenEndpointResponse | ClientCredentialsGrantResponse | DeviceAuthorizationResponse | IntrospectionResponse | OAuth2Error | PushedAuthorizationResponse | URLSearchParams | UserInfoResponse): input is OAuth2Error;
|
|
1004
1125
|
export interface WWWAuthenticateChallengeParameters {
|
|
@@ -1036,6 +1157,9 @@ export interface WWWAuthenticateChallenge {
|
|
|
1036
1157
|
* @group Token Revocation
|
|
1037
1158
|
* @group Refreshing an Access Token
|
|
1038
1159
|
* @group Pushed Authorization Requests (PAR)
|
|
1160
|
+
* @group JWT Bearer Token Grant Type
|
|
1161
|
+
* @group SAML 2.0 Bearer Assertion Grant Type
|
|
1162
|
+
* @group Token Exchange Grant Type
|
|
1039
1163
|
*/
|
|
1040
1164
|
export declare function parseWwwAuthenticateChallenges(response: Response): WWWAuthenticateChallenge[] | undefined;
|
|
1041
1165
|
/**
|
|
@@ -1252,7 +1376,7 @@ export declare function validateIdTokenSignature(as: AuthorizationServer, ref: O
|
|
|
1252
1376
|
*
|
|
1253
1377
|
* @see [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html#UserInfo)
|
|
1254
1378
|
*/
|
|
1255
|
-
export declare function
|
|
1379
|
+
export declare function validateJwtUserInfoSignature(as: AuthorizationServer, ref: Response, options?: ValidateSignatureOptions): Promise<void>;
|
|
1256
1380
|
/**
|
|
1257
1381
|
* Validates the JWS Signature of an JWT {@link !Response} body of responses previously processed by
|
|
1258
1382
|
* {@link processIntrospectionResponse} for non-repudiation purposes.
|
|
@@ -1461,6 +1585,24 @@ export interface ClientCredentialsGrantRequestOptions extends HttpRequestOptions
|
|
|
1461
1585
|
* @see [RFC 9449 - OAuth 2.0 Demonstrating Proof-of-Possession at the Application Layer (DPoP)](https://www.rfc-editor.org/rfc/rfc9449.html#name-dpop-access-token-request)
|
|
1462
1586
|
*/
|
|
1463
1587
|
export declare function clientCredentialsGrantRequest(as: AuthorizationServer, client: Client, parameters: URLSearchParams | Record<string, string> | string[][], options?: ClientCredentialsGrantRequestOptions): Promise<Response>;
|
|
1588
|
+
/**
|
|
1589
|
+
* Performs any Grant request at the {@link AuthorizationServer.token_endpoint `as.token_endpoint`}.
|
|
1590
|
+
* The purpose is to be able to execute grant requests such as Token Exchange Grant Type, JWT Bearer
|
|
1591
|
+
* Token Grant Type, or SAML 2.0 Bearer Assertion Grant Type.
|
|
1592
|
+
*
|
|
1593
|
+
* @param as Authorization Server Metadata.
|
|
1594
|
+
* @param client Client Metadata.
|
|
1595
|
+
* @param grantType Grant Type.
|
|
1596
|
+
*
|
|
1597
|
+
* @group JWT Bearer Token Grant Type
|
|
1598
|
+
* @group SAML 2.0 Bearer Assertion Grant Type
|
|
1599
|
+
* @group Token Exchange Grant Type
|
|
1600
|
+
*
|
|
1601
|
+
* @see {@link https://www.rfc-editor.org/rfc/rfc8693.html Token Exchange Grant Type}
|
|
1602
|
+
* @see {@link https://www.rfc-editor.org/rfc/rfc7523.html#section-2.1 JWT Bearer Token Grant Type}
|
|
1603
|
+
* @see {@link https://www.rfc-editor.org/rfc/rfc7522.html#section-2.1 SAML 2.0 Bearer Assertion Grant Type}
|
|
1604
|
+
*/
|
|
1605
|
+
export declare function genericTokenEndpointRequest(as: AuthorizationServer, client: Client, grantType: string, parameters: URLSearchParams | Record<string, string> | string[][], options?: Omit<TokenEndpointRequestOptions, 'additionalParameters'>): Promise<Response>;
|
|
1464
1606
|
/**
|
|
1465
1607
|
* Validates Client Credentials Grant {@link !Response} instance to be one coming from the
|
|
1466
1608
|
* {@link AuthorizationServer.token_endpoint `as.token_endpoint`}.
|
|
@@ -1587,6 +1729,9 @@ export declare function processIntrospectionResponse(as: AuthorizationServer, cl
|
|
|
1587
1729
|
export interface JWKS {
|
|
1588
1730
|
readonly keys: JWK[];
|
|
1589
1731
|
}
|
|
1732
|
+
export interface JweDecryptFunction {
|
|
1733
|
+
(jwe: string): Promise<string>;
|
|
1734
|
+
}
|
|
1590
1735
|
/**
|
|
1591
1736
|
* Same as {@link validateAuthResponse} but for signed JARM responses.
|
|
1592
1737
|
*
|
|
@@ -1864,6 +2009,12 @@ export declare const experimental_validateDetachedSignatureResponse: (as: Author
|
|
|
1864
2009
|
* @deprecated Use {@link validateJwtAccessToken}.
|
|
1865
2010
|
*/
|
|
1866
2011
|
export declare const experimental_validateJwtAccessToken: (as: AuthorizationServer, request: Request, expectedAudience: string, options?: ValidateJWTAccessTokenOptions | undefined) => ReturnType<typeof validateJwtAccessToken>;
|
|
2012
|
+
/**
|
|
2013
|
+
* @ignore
|
|
2014
|
+
*
|
|
2015
|
+
* @deprecated Use {@link validateJwtUserinfoSignature}.
|
|
2016
|
+
*/
|
|
2017
|
+
export declare const validateJwtUserinfoSignature: (as: AuthorizationServer, ref: Response, options?: ValidateSignatureOptions | undefined) => ReturnType<typeof validateJwtUserInfoSignature>;
|
|
1867
2018
|
/**
|
|
1868
2019
|
* @ignore
|
|
1869
2020
|
*
|
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.16.0';
|
|
5
5
|
USER_AGENT = `${NAME}/${VERSION}`;
|
|
6
6
|
}
|
|
7
7
|
function looseInstanceOf(input, expected) {
|
|
@@ -19,6 +19,8 @@ function looseInstanceOf(input, expected) {
|
|
|
19
19
|
export const clockSkew = Symbol();
|
|
20
20
|
export const clockTolerance = Symbol();
|
|
21
21
|
export const customFetch = Symbol();
|
|
22
|
+
export const modifyAssertion = Symbol();
|
|
23
|
+
export const jweDecrypt = Symbol();
|
|
22
24
|
export const jwksCache = Symbol();
|
|
23
25
|
export const useMtlsAlias = Symbol();
|
|
24
26
|
const encoder = new TextEncoder();
|
|
@@ -279,7 +281,11 @@ function getKeyAndKid(input) {
|
|
|
279
281
|
if (input.kid !== undefined && !validateString(input.kid)) {
|
|
280
282
|
throw new TypeError('"kid" must be a non-empty string');
|
|
281
283
|
}
|
|
282
|
-
return {
|
|
284
|
+
return {
|
|
285
|
+
key: input.key,
|
|
286
|
+
kid: input.kid,
|
|
287
|
+
modifyAssertion: input[modifyAssertion],
|
|
288
|
+
};
|
|
283
289
|
}
|
|
284
290
|
function formUrlEncode(token) {
|
|
285
291
|
return encodeURIComponent(token).replace(/%20/g, '+');
|
|
@@ -366,11 +372,11 @@ function clientAssertion(as, client) {
|
|
|
366
372
|
sub: client.client_id,
|
|
367
373
|
};
|
|
368
374
|
}
|
|
369
|
-
async function privateKeyJwt(as, client, key, kid) {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
375
|
+
async function privateKeyJwt(as, client, key, kid, modifyAssertion) {
|
|
376
|
+
const header = { alg: keyToJws(key), kid };
|
|
377
|
+
const payload = clientAssertion(as, client);
|
|
378
|
+
modifyAssertion?.(header, payload);
|
|
379
|
+
return jwt(header, payload, key);
|
|
374
380
|
}
|
|
375
381
|
function assertAs(as) {
|
|
376
382
|
if (typeof as !== 'object' || as === null) {
|
|
@@ -428,13 +434,13 @@ async function clientAuthentication(as, client, body, headers, clientPrivateKey)
|
|
|
428
434
|
if (clientPrivateKey === undefined) {
|
|
429
435
|
throw new TypeError('"options.clientPrivateKey" must be provided when "client.token_endpoint_auth_method" is "private_key_jwt"');
|
|
430
436
|
}
|
|
431
|
-
const { key, kid } = getKeyAndKid(clientPrivateKey);
|
|
437
|
+
const { key, kid, modifyAssertion } = getKeyAndKid(clientPrivateKey);
|
|
432
438
|
if (!isPrivateKey(key)) {
|
|
433
439
|
throw new TypeError('"options.clientPrivateKey.key" must be a private CryptoKey');
|
|
434
440
|
}
|
|
435
441
|
body.set('client_id', client.client_id);
|
|
436
442
|
body.set('client_assertion_type', 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer');
|
|
437
|
-
body.set('client_assertion', await privateKeyJwt(as, client, key, kid));
|
|
443
|
+
body.set('client_assertion', await privateKeyJwt(as, client, key, kid, modifyAssertion));
|
|
438
444
|
break;
|
|
439
445
|
}
|
|
440
446
|
case 'tls_client_auth':
|
|
@@ -449,11 +455,11 @@ async function clientAuthentication(as, client, body, headers, clientPrivateKey)
|
|
|
449
455
|
throw new UnsupportedOperationError('unsupported client token_endpoint_auth_method');
|
|
450
456
|
}
|
|
451
457
|
}
|
|
452
|
-
async function jwt(header,
|
|
458
|
+
async function jwt(header, payload, key) {
|
|
453
459
|
if (!key.usages.includes('sign')) {
|
|
454
460
|
throw new TypeError('CryptoKey instances used for signing assertions must include "sign" in their "usages"');
|
|
455
461
|
}
|
|
456
|
-
const input = `${b64u(buf(JSON.stringify(header)))}.${b64u(buf(JSON.stringify(
|
|
462
|
+
const input = `${b64u(buf(JSON.stringify(header)))}.${b64u(buf(JSON.stringify(payload)))}`;
|
|
457
463
|
const signature = b64u(await crypto.subtle.sign(keyToSubtle(key), key, buf(input)));
|
|
458
464
|
return `${input}.${signature}`;
|
|
459
465
|
}
|
|
@@ -461,7 +467,7 @@ export async function issueRequestObject(as, client, parameters, privateKey) {
|
|
|
461
467
|
assertAs(as);
|
|
462
468
|
assertClient(client);
|
|
463
469
|
parameters = new URLSearchParams(parameters);
|
|
464
|
-
const { key, kid } = getKeyAndKid(privateKey);
|
|
470
|
+
const { key, kid, modifyAssertion } = getKeyAndKid(privateKey);
|
|
465
471
|
if (!isPrivateKey(key)) {
|
|
466
472
|
throw new TypeError('"privateKey.key" must be a private CryptoKey');
|
|
467
473
|
}
|
|
@@ -519,11 +525,13 @@ export async function issueRequestObject(as, client, parameters, privateKey) {
|
|
|
519
525
|
}
|
|
520
526
|
}
|
|
521
527
|
}
|
|
522
|
-
|
|
528
|
+
const header = {
|
|
523
529
|
alg: keyToJws(key),
|
|
524
530
|
typ: 'oauth-authz-req+jwt',
|
|
525
531
|
kid,
|
|
526
|
-
}
|
|
532
|
+
};
|
|
533
|
+
modifyAssertion?.(header, claims);
|
|
534
|
+
return jwt(header, claims, key);
|
|
527
535
|
}
|
|
528
536
|
async function dpopProofJwt(headers, options, url, htm, clockSkew, accessToken) {
|
|
529
537
|
const { privateKey, publicKey, nonce = dpopNonces.get(url.origin) } = options;
|
|
@@ -540,19 +548,21 @@ async function dpopProofJwt(headers, options, url, htm, clockSkew, accessToken)
|
|
|
540
548
|
throw new TypeError('"DPoP.publicKey.extractable" must be true');
|
|
541
549
|
}
|
|
542
550
|
const now = epochTime() + clockSkew;
|
|
543
|
-
const
|
|
551
|
+
const header = {
|
|
544
552
|
alg: keyToJws(privateKey),
|
|
545
553
|
typ: 'dpop+jwt',
|
|
546
554
|
jwk: await publicJwk(publicKey),
|
|
547
|
-
}
|
|
555
|
+
};
|
|
556
|
+
const payload = {
|
|
548
557
|
iat: now,
|
|
549
558
|
jti: randomBytes(),
|
|
550
559
|
htm,
|
|
551
560
|
nonce,
|
|
552
561
|
htu: `${url.origin}${url.pathname}`,
|
|
553
562
|
ath: accessToken ? b64u(await crypto.subtle.digest('SHA-256', buf(accessToken))) : undefined,
|
|
554
|
-
}
|
|
555
|
-
|
|
563
|
+
};
|
|
564
|
+
options[modifyAssertion]?.(header, payload);
|
|
565
|
+
headers.set('dpop', await jwt(header, payload, privateKey));
|
|
556
566
|
}
|
|
557
567
|
let jwkCache;
|
|
558
568
|
async function getSetPublicJwkCache(key) {
|
|
@@ -858,8 +868,7 @@ export async function processUserInfoResponse(as, client, expectedSubject, respo
|
|
|
858
868
|
let json;
|
|
859
869
|
if (getContentType(response) === 'application/jwt') {
|
|
860
870
|
assertReadableResponse(response);
|
|
861
|
-
const jwt = await response.text()
|
|
862
|
-
const { claims } = await validateJwt(jwt, checkSigningAlgorithm.bind(undefined, client.userinfo_signed_response_alg, as.userinfo_signing_alg_values_supported), noSignatureCheck, getClockSkew(client), getClockTolerance(client))
|
|
871
|
+
const { claims, jwt } = await validateJwt(await response.text(), checkSigningAlgorithm.bind(undefined, client.userinfo_signed_response_alg, as.userinfo_signing_alg_values_supported), noSignatureCheck, getClockSkew(client), getClockTolerance(client), client[jweDecrypt])
|
|
863
872
|
.then(validateOptionalAudience.bind(undefined, client.client_id))
|
|
864
873
|
.then(validateOptionalIssuer.bind(undefined, as.issuer));
|
|
865
874
|
jwtResponseBodies.set(response, jwt);
|
|
@@ -937,14 +946,14 @@ export function getValidatedIdTokenClaims(ref) {
|
|
|
937
946
|
if (!claims) {
|
|
938
947
|
throw new TypeError('"ref" was already garbage collected or did not resolve from the proper sources');
|
|
939
948
|
}
|
|
940
|
-
return claims;
|
|
949
|
+
return claims[0];
|
|
941
950
|
}
|
|
942
951
|
export async function validateIdTokenSignature(as, ref, options) {
|
|
943
952
|
assertAs(as);
|
|
944
|
-
if (!
|
|
953
|
+
if (!idTokenClaims.has(ref)) {
|
|
945
954
|
throw new OPE('"ref" does not contain an ID Token to verify the signature of');
|
|
946
955
|
}
|
|
947
|
-
const { 0: protectedHeader, 1: payload, 2: encodedSignature } = ref.
|
|
956
|
+
const { 0: protectedHeader, 1: payload, 2: encodedSignature, } = idTokenClaims.get(ref)[1].split('.');
|
|
948
957
|
const header = JSON.parse(buf(b64u(protectedHeader)));
|
|
949
958
|
if (header.alg.startsWith('HS')) {
|
|
950
959
|
throw new UnsupportedOperationError();
|
|
@@ -967,7 +976,7 @@ async function validateJwtResponseSignature(as, ref, options) {
|
|
|
967
976
|
key = await getPublicSigKeyFromIssuerJwksUri(as, options, header);
|
|
968
977
|
await validateJwsSignature(protectedHeader, payload, key, b64u(encodedSignature));
|
|
969
978
|
}
|
|
970
|
-
export function
|
|
979
|
+
export function validateJwtUserInfoSignature(as, ref, options) {
|
|
971
980
|
return validateJwtResponseSignature(as, ref, options);
|
|
972
981
|
}
|
|
973
982
|
export function validateJwtIntrospectionSignature(as, ref, options) {
|
|
@@ -1024,7 +1033,7 @@ async function processGenericAccessTokenResponse(as, client, response, ignoreIdT
|
|
|
1024
1033
|
throw new OPE('"response" body "id_token" property must be a non-empty string');
|
|
1025
1034
|
}
|
|
1026
1035
|
if (json.id_token) {
|
|
1027
|
-
const { claims } = await validateJwt(json.id_token, checkSigningAlgorithm.bind(undefined, client.id_token_signed_response_alg, as.id_token_signing_alg_values_supported), noSignatureCheck, getClockSkew(client), getClockTolerance(client))
|
|
1036
|
+
const { claims, jwt } = await validateJwt(json.id_token, checkSigningAlgorithm.bind(undefined, client.id_token_signed_response_alg, as.id_token_signing_alg_values_supported), noSignatureCheck, getClockSkew(client), getClockTolerance(client), client[jweDecrypt])
|
|
1028
1037
|
.then(validatePresence.bind(undefined, ['aud', 'exp', 'iat', 'iss', 'sub']))
|
|
1029
1038
|
.then(validateIssuer.bind(undefined, as.issuer))
|
|
1030
1039
|
.then(validateAudience.bind(undefined, client.client_id));
|
|
@@ -1040,7 +1049,7 @@ async function processGenericAccessTokenResponse(as, client, response, ignoreIdT
|
|
|
1040
1049
|
(!Number.isFinite(claims.auth_time) || Math.sign(claims.auth_time) !== 1)) {
|
|
1041
1050
|
throw new OPE('ID Token "auth_time" (authentication time) must be a positive number');
|
|
1042
1051
|
}
|
|
1043
|
-
idTokenClaims.set(json, claims);
|
|
1052
|
+
idTokenClaims.set(json, [claims, jwt]);
|
|
1044
1053
|
}
|
|
1045
1054
|
}
|
|
1046
1055
|
return json;
|
|
@@ -1198,6 +1207,14 @@ export async function clientCredentialsGrantRequest(as, client, parameters, opti
|
|
|
1198
1207
|
assertClient(client);
|
|
1199
1208
|
return tokenEndpointRequest(as, client, 'client_credentials', new URLSearchParams(parameters), options);
|
|
1200
1209
|
}
|
|
1210
|
+
export async function genericTokenEndpointRequest(as, client, grantType, parameters, options) {
|
|
1211
|
+
assertAs(as);
|
|
1212
|
+
assertClient(client);
|
|
1213
|
+
if (!validateString(grantType)) {
|
|
1214
|
+
throw new TypeError('"grantType" must be a non-empty string');
|
|
1215
|
+
}
|
|
1216
|
+
return tokenEndpointRequest(as, client, grantType, new URLSearchParams(parameters), options);
|
|
1217
|
+
}
|
|
1201
1218
|
export async function processClientCredentialsResponse(as, client, response) {
|
|
1202
1219
|
const result = await processGenericAccessTokenResponse(as, client, response, true, true);
|
|
1203
1220
|
if (isOAuth2Error(result)) {
|
|
@@ -1270,8 +1287,7 @@ export async function processIntrospectionResponse(as, client, response) {
|
|
|
1270
1287
|
let json;
|
|
1271
1288
|
if (getContentType(response) === 'application/token-introspection+jwt') {
|
|
1272
1289
|
assertReadableResponse(response);
|
|
1273
|
-
const jwt = await response.text()
|
|
1274
|
-
const { claims } = await validateJwt(jwt, checkSigningAlgorithm.bind(undefined, client.introspection_signed_response_alg, as.introspection_signing_alg_values_supported), noSignatureCheck, getClockSkew(client), getClockTolerance(client))
|
|
1290
|
+
const { claims, jwt } = await validateJwt(await response.text(), checkSigningAlgorithm.bind(undefined, client.introspection_signed_response_alg, as.introspection_signing_alg_values_supported), noSignatureCheck, getClockSkew(client), getClockTolerance(client), client[jweDecrypt])
|
|
1275
1291
|
.then(checkJwtType.bind(undefined, 'token-introspection+jwt'))
|
|
1276
1292
|
.then(validatePresence.bind(undefined, ['aud', 'iat', 'iss']))
|
|
1277
1293
|
.then(validateIssuer.bind(undefined, as.issuer))
|
|
@@ -1424,10 +1440,16 @@ async function validateJwsSignature(protectedHeader, payload, key, signature) {
|
|
|
1424
1440
|
throw new OPE('JWT signature verification failed');
|
|
1425
1441
|
}
|
|
1426
1442
|
}
|
|
1427
|
-
async function validateJwt(jws, checkAlg, getKey, clockSkew, clockTolerance) {
|
|
1428
|
-
|
|
1443
|
+
async function validateJwt(jws, checkAlg, getKey, clockSkew, clockTolerance, decryptJwt) {
|
|
1444
|
+
let { 0: protectedHeader, 1: payload, 2: encodedSignature, length } = jws.split('.');
|
|
1429
1445
|
if (length === 5) {
|
|
1430
|
-
|
|
1446
|
+
if (decryptJwt !== undefined) {
|
|
1447
|
+
jws = await decryptJwt(jws);
|
|
1448
|
+
({ 0: protectedHeader, 1: payload, 2: encodedSignature, length } = jws.split('.'));
|
|
1449
|
+
}
|
|
1450
|
+
else {
|
|
1451
|
+
throw new UnsupportedOperationError('JWE structure JWTs are not supported');
|
|
1452
|
+
}
|
|
1431
1453
|
}
|
|
1432
1454
|
if (length !== 3) {
|
|
1433
1455
|
throw new OPE('Invalid JWT');
|
|
@@ -1494,7 +1516,7 @@ async function validateJwt(jws, checkAlg, getKey, clockSkew, clockTolerance) {
|
|
|
1494
1516
|
throw new OPE('unexpected JWT "aud" (audience) claim type');
|
|
1495
1517
|
}
|
|
1496
1518
|
}
|
|
1497
|
-
return { header, claims, signature, key };
|
|
1519
|
+
return { header, claims, signature, key, jwt: jws };
|
|
1498
1520
|
}
|
|
1499
1521
|
export async function validateJwtAuthResponse(as, client, parameters, expectedState, options) {
|
|
1500
1522
|
assertAs(as);
|
|
@@ -1509,7 +1531,7 @@ export async function validateJwtAuthResponse(as, client, parameters, expectedSt
|
|
|
1509
1531
|
if (!response) {
|
|
1510
1532
|
throw new OPE('"parameters" does not contain a JARM response');
|
|
1511
1533
|
}
|
|
1512
|
-
const { claims } = await validateJwt(response, checkSigningAlgorithm.bind(undefined, client.authorization_signed_response_alg, as.authorization_signing_alg_values_supported), getPublicSigKeyFromIssuerJwksUri.bind(undefined, as, options), getClockSkew(client), getClockTolerance(client))
|
|
1534
|
+
const { claims } = await validateJwt(response, checkSigningAlgorithm.bind(undefined, client.authorization_signed_response_alg, as.authorization_signing_alg_values_supported), getPublicSigKeyFromIssuerJwksUri.bind(undefined, as, options), getClockSkew(client), getClockTolerance(client), client[jweDecrypt])
|
|
1513
1535
|
.then(validatePresence.bind(undefined, ['aud', 'exp', 'iss']))
|
|
1514
1536
|
.then(validateIssuer.bind(undefined, as.issuer))
|
|
1515
1537
|
.then(validateAudience.bind(undefined, client.client_id));
|
|
@@ -1605,7 +1627,7 @@ export async function validateDetachedSignatureResponse(as, client, parameters,
|
|
|
1605
1627
|
if (typeof expectedState === 'string') {
|
|
1606
1628
|
requiredClaims.push('s_hash');
|
|
1607
1629
|
}
|
|
1608
|
-
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))
|
|
1630
|
+
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), client[jweDecrypt])
|
|
1609
1631
|
.then(validatePresence.bind(undefined, requiredClaims))
|
|
1610
1632
|
.then(validateIssuer.bind(undefined, as.issuer))
|
|
1611
1633
|
.then(validateAudience.bind(undefined, client.client_id));
|
|
@@ -1882,7 +1904,7 @@ async function validateDPoP(as, request, accessToken, accessTokenClaims, options
|
|
|
1882
1904
|
throw new OPE('DPoP Proof jwk header parameter must contain a public key');
|
|
1883
1905
|
}
|
|
1884
1906
|
return key;
|
|
1885
|
-
}, clockSkew, getClockTolerance(options))
|
|
1907
|
+
}, clockSkew, getClockTolerance(options), undefined)
|
|
1886
1908
|
.then(checkJwtType.bind(undefined, 'dpop+jwt'))
|
|
1887
1909
|
.then(validatePresence.bind(undefined, ['iat', 'jti', 'ath', 'htm', 'htu']));
|
|
1888
1910
|
const now = epochTime() + clockSkew;
|
|
@@ -1973,7 +1995,7 @@ export async function validateJwtAccessToken(as, request, expectedAudience, opti
|
|
|
1973
1995
|
if (options?.requireDPoP || scheme === 'dpop' || request.headers.has('dpop')) {
|
|
1974
1996
|
requiredClaims.push('cnf');
|
|
1975
1997
|
}
|
|
1976
|
-
const { claims } = await validateJwt(accessToken, checkSigningAlgorithm.bind(undefined, undefined, SUPPORTED_JWS_ALGS), getPublicSigKeyFromIssuerJwksUri.bind(undefined, as, options), getClockSkew(options), getClockTolerance(options))
|
|
1998
|
+
const { claims } = await validateJwt(accessToken, checkSigningAlgorithm.bind(undefined, undefined, SUPPORTED_JWS_ALGS), getPublicSigKeyFromIssuerJwksUri.bind(undefined, as, options), getClockSkew(options), getClockTolerance(options), undefined)
|
|
1977
1999
|
.then(checkJwtType.bind(undefined, 'at+jwt'))
|
|
1978
2000
|
.then(validatePresence.bind(undefined, requiredClaims))
|
|
1979
2001
|
.then(validateIssuer.bind(undefined, as.issuer))
|
|
@@ -2011,4 +2033,5 @@ export const experimentalUseMtlsAlias = useMtlsAlias;
|
|
|
2011
2033
|
export const experimental_useMtlsAlias = useMtlsAlias;
|
|
2012
2034
|
export const experimental_validateDetachedSignatureResponse = (...args) => validateDetachedSignatureResponse(...args);
|
|
2013
2035
|
export const experimental_validateJwtAccessToken = (...args) => validateJwtAccessToken(...args);
|
|
2036
|
+
export const validateJwtUserinfoSignature = (...args) => validateJwtUserInfoSignature(...args);
|
|
2014
2037
|
export const experimental_jwksCache = jwksCache;
|