oauth4webapi 2.0.6 → 2.2.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 +1 -1
- package/build/index.d.ts +105 -9
- package/build/index.js +131 -72
- package/package.json +15 -15
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ import * as oauth2 from 'oauth4webapi'
|
|
|
39
39
|
**`example`** Deno import
|
|
40
40
|
|
|
41
41
|
```js
|
|
42
|
-
import * as oauth2 from 'https://deno.land/x/oauth4webapi@v2.0
|
|
42
|
+
import * as oauth2 from 'https://deno.land/x/oauth4webapi@v2.2.0/mod.ts'
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
- Authorization Code Flow - OpenID Connect [source](examples/code.ts), or plain OAuth 2 [source](examples/oauth.ts)
|
package/build/index.d.ts
CHANGED
|
@@ -45,31 +45,67 @@ export type ClientAuthenticationMethod = 'client_secret_basic' | 'client_secret_
|
|
|
45
45
|
/**
|
|
46
46
|
* Supported JWS `alg` Algorithm identifiers.
|
|
47
47
|
*
|
|
48
|
-
* @example CryptoKey algorithm for the `PS256` JWS Algorithm
|
|
48
|
+
* @example CryptoKey algorithm for the `PS256`, `PS384`, or `PS512` JWS Algorithm Identifiers
|
|
49
49
|
*
|
|
50
50
|
* ```ts
|
|
51
|
-
* interface
|
|
51
|
+
* interface RSAPSSAlgorithm extends RsaHashedKeyAlgorithm {
|
|
52
52
|
* name: 'RSA-PSS'
|
|
53
|
+
* hash: { name: 'SHA-256' | 'SHA-384' | 'SHA-512' }
|
|
54
|
+
* }
|
|
55
|
+
*
|
|
56
|
+
* interface PS256 extends RSAPSSAlgorithm {
|
|
53
57
|
* hash: { name: 'SHA-256' }
|
|
54
58
|
* }
|
|
59
|
+
*
|
|
60
|
+
* interface PS384 extends RSAPSSAlgorithm {
|
|
61
|
+
* hash: { name: 'SHA-384' }
|
|
62
|
+
* }
|
|
63
|
+
*
|
|
64
|
+
* interface PS512 extends RSAPSSAlgorithm {
|
|
65
|
+
* hash: { name: 'SHA-512' }
|
|
66
|
+
* }
|
|
55
67
|
* ```
|
|
56
68
|
*
|
|
57
|
-
* @example CryptoKey algorithm for the `ES256` JWS Algorithm
|
|
69
|
+
* @example CryptoKey algorithm for the `ES256`, `ES384`, or `ES512` JWS Algorithm Identifiers
|
|
58
70
|
*
|
|
59
71
|
* ```ts
|
|
60
|
-
* interface
|
|
72
|
+
* interface ECDSAAlgorithm extends EcKeyAlgorithm {
|
|
61
73
|
* name: 'ECDSA'
|
|
74
|
+
* namedCurve: 'P-256' | 'P-384' | 'P-521'
|
|
75
|
+
* }
|
|
76
|
+
*
|
|
77
|
+
* interface ES256 extends ECDSAAlgorithm {
|
|
62
78
|
* namedCurve: 'P-256'
|
|
63
79
|
* }
|
|
80
|
+
*
|
|
81
|
+
* interface ES384 extends ECDSAAlgorithm {
|
|
82
|
+
* namedCurve: 'P-384'
|
|
83
|
+
* }
|
|
84
|
+
*
|
|
85
|
+
* interface ES512 extends ECDSAAlgorithm {
|
|
86
|
+
* namedCurve: 'P-521'
|
|
87
|
+
* }
|
|
64
88
|
* ```
|
|
65
89
|
*
|
|
66
|
-
* @example CryptoKey algorithm for the `RS256` JWS Algorithm
|
|
90
|
+
* @example CryptoKey algorithm for the `RS256`, `RS384`, or `RS512` JWS Algorithm Identifiers
|
|
67
91
|
*
|
|
68
92
|
* ```ts
|
|
69
|
-
* interface
|
|
93
|
+
* interface ECDSAAlgorithm extends RsaHashedKeyAlgorithm {
|
|
70
94
|
* name: 'RSASSA-PKCS1-v1_5'
|
|
95
|
+
* hash: { name: 'SHA-256' | 'SHA-384' | 'SHA-512' }
|
|
96
|
+
* }
|
|
97
|
+
*
|
|
98
|
+
* interface RS256 extends ECDSAAlgorithm {
|
|
71
99
|
* hash: { name: 'SHA-256' }
|
|
72
100
|
* }
|
|
101
|
+
*
|
|
102
|
+
* interface RS384 extends ECDSAAlgorithm {
|
|
103
|
+
* hash: { name: 'SHA-384' }
|
|
104
|
+
* }
|
|
105
|
+
*
|
|
106
|
+
* interface RS512 extends ECDSAAlgorithm {
|
|
107
|
+
* hash: { name: 'SHA-512' }
|
|
108
|
+
* }
|
|
73
109
|
* ```
|
|
74
110
|
*
|
|
75
111
|
* @example CryptoKey algorithm for the `EdDSA` JWS Algorithm Identifier (Experimental)
|
|
@@ -79,12 +115,16 @@ export type ClientAuthenticationMethod = 'client_secret_basic' | 'client_secret_
|
|
|
79
115
|
* widely adopted. If the proposal changes this implementation will follow up with a minor release.
|
|
80
116
|
*
|
|
81
117
|
* ```ts
|
|
82
|
-
* interface
|
|
83
|
-
* name: 'Ed25519'
|
|
118
|
+
* interface EdDSA extends KeyAlgorithm {
|
|
119
|
+
* name: 'Ed25519' | 'Ed448'
|
|
84
120
|
* }
|
|
85
121
|
* ```
|
|
86
122
|
*/
|
|
87
|
-
export type JWSAlgorithm = 'PS256' | 'ES256' | 'RS256' | 'EdDSA';
|
|
123
|
+
export type JWSAlgorithm = 'PS256' | 'ES256' | 'RS256' | 'EdDSA' | 'ES384' | 'PS384' | 'RS384' | 'ES512' | 'PS512' | 'RS512';
|
|
124
|
+
/** @ignore during Documentation generation but part of the public API */
|
|
125
|
+
export declare const clockSkew: unique symbol;
|
|
126
|
+
/** @ignore during Documentation generation but part of the public API */
|
|
127
|
+
export declare const clockTolerance: unique symbol;
|
|
88
128
|
/**
|
|
89
129
|
* Authorization Server Metadata
|
|
90
130
|
*
|
|
@@ -406,6 +446,50 @@ export interface Client {
|
|
|
406
446
|
introspection_signed_response_alg?: string;
|
|
407
447
|
/** Default Maximum Authentication Age. */
|
|
408
448
|
default_max_age?: number;
|
|
449
|
+
/**
|
|
450
|
+
* Use to adjust the client's assumed current time. Positive and negative finite values
|
|
451
|
+
* representing seconds are allowed. Default is `0` (Date.now() + 0 seconds is used).
|
|
452
|
+
*
|
|
453
|
+
* @ignore during Documentation generation but part of the public API
|
|
454
|
+
*
|
|
455
|
+
* @example Client's local clock is mistakenly 1 hour in the past
|
|
456
|
+
*
|
|
457
|
+
* ```ts
|
|
458
|
+
* const client: oauth.Client = {
|
|
459
|
+
* client_id: 'abc4ba37-4ab8-49b5-99d4-9441ba35d428',
|
|
460
|
+
* // ... other metadata
|
|
461
|
+
* [oauth.clockSkew]: +(60 * 60),
|
|
462
|
+
* }
|
|
463
|
+
* ```
|
|
464
|
+
*
|
|
465
|
+
* @example Client's local clock is mistakenly 1 hour in the future
|
|
466
|
+
*
|
|
467
|
+
* ```ts
|
|
468
|
+
* const client: oauth.Client = {
|
|
469
|
+
* client_id: 'abc4ba37-4ab8-49b5-99d4-9441ba35d428',
|
|
470
|
+
* // ... other metadata
|
|
471
|
+
* [oauth.clockSkew]: -(60 * 60),
|
|
472
|
+
* }
|
|
473
|
+
* ```
|
|
474
|
+
*/
|
|
475
|
+
[clockSkew]?: number;
|
|
476
|
+
/**
|
|
477
|
+
* Use to set allowed client's clock tolerance when checking DateTime JWT Claims. Only positive
|
|
478
|
+
* finite values representing seconds are allowed. Default is `30` (30 seconds).
|
|
479
|
+
*
|
|
480
|
+
* @ignore during Documentation generation but part of the public API
|
|
481
|
+
*
|
|
482
|
+
* @example Tolerate 30 seconds clock skew when validating JWT claims like `exp` or `nbf`.
|
|
483
|
+
*
|
|
484
|
+
* ```ts
|
|
485
|
+
* const client: oauth.Client = {
|
|
486
|
+
* client_id: 'abc4ba37-4ab8-49b5-99d4-9441ba35d428',
|
|
487
|
+
* // ... other metadata
|
|
488
|
+
* [oauth.clockTolerance]: 30,
|
|
489
|
+
* }
|
|
490
|
+
* ```
|
|
491
|
+
*/
|
|
492
|
+
[clockTolerance]?: number;
|
|
409
493
|
[metadata: string]: JsonValue | undefined;
|
|
410
494
|
}
|
|
411
495
|
export declare class UnsupportedOperationError extends Error {
|
|
@@ -593,6 +677,16 @@ export declare function parseWwwAuthenticateChallenges(response: Response): WWWA
|
|
|
593
677
|
*/
|
|
594
678
|
export declare function processPushedAuthorizationResponse(as: AuthorizationServer, client: Client, response: Response): Promise<PushedAuthorizationResponse | OAuth2Error>;
|
|
595
679
|
export interface ProtectedResourceRequestOptions extends Omit<HttpRequestOptions, 'headers'>, DPoPRequestOptions {
|
|
680
|
+
/**
|
|
681
|
+
* Use to adjust the client's assumed current time. Positive and negative finite values
|
|
682
|
+
* representing seconds are allowed. Default is `0` (Date.now() + 0 seconds is used).
|
|
683
|
+
*
|
|
684
|
+
* This option only affects the request if the {@link ProtectedResourceRequestOptions.DPoP DPoP}
|
|
685
|
+
* option is also used.
|
|
686
|
+
*
|
|
687
|
+
* @ignore during Documentation generation but part of the public API
|
|
688
|
+
*/
|
|
689
|
+
clockSkew?: number;
|
|
596
690
|
}
|
|
597
691
|
/**
|
|
598
692
|
* Performs a protected resource request at an arbitrary URL.
|
|
@@ -1082,6 +1176,8 @@ export interface GenerateKeyPairOptions {
|
|
|
1082
1176
|
extractable?: boolean;
|
|
1083
1177
|
/** (RSA algorithms only) The length, in bits, of the RSA modulus. Default is `2048`. */
|
|
1084
1178
|
modulusLength?: number;
|
|
1179
|
+
/** (EdDSA algorithms only) The EdDSA sub-type. Default is `Ed25519`. */
|
|
1180
|
+
crv?: 'Ed25519' | 'Ed448';
|
|
1085
1181
|
}
|
|
1086
1182
|
/**
|
|
1087
1183
|
* Generates a CryptoKeyPair for a given JWS `alg` Algorithm identifier.
|
package/build/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
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.0
|
|
4
|
+
const VERSION = 'v2.2.0';
|
|
5
5
|
USER_AGENT = `${NAME}/${VERSION}`;
|
|
6
6
|
}
|
|
7
|
+
export const clockSkew = Symbol();
|
|
8
|
+
export const clockTolerance = Symbol();
|
|
7
9
|
const encoder = new TextEncoder();
|
|
8
10
|
const decoder = new TextDecoder();
|
|
9
11
|
function buf(input) {
|
|
@@ -113,7 +115,18 @@ function isPrivateKey(key) {
|
|
|
113
115
|
function isPublicKey(key) {
|
|
114
116
|
return isCryptoKey(key) && key.type === 'public';
|
|
115
117
|
}
|
|
116
|
-
const SUPPORTED_JWS_ALGS = [
|
|
118
|
+
const SUPPORTED_JWS_ALGS = [
|
|
119
|
+
'PS256',
|
|
120
|
+
'ES256',
|
|
121
|
+
'RS256',
|
|
122
|
+
'PS384',
|
|
123
|
+
'ES384',
|
|
124
|
+
'RS384',
|
|
125
|
+
'PS512',
|
|
126
|
+
'ES512',
|
|
127
|
+
'RS512',
|
|
128
|
+
'EdDSA',
|
|
129
|
+
];
|
|
117
130
|
function processDpopNonce(response) {
|
|
118
131
|
const url = new URL(response.url);
|
|
119
132
|
if (response.headers.has('dpop-nonce')) {
|
|
@@ -263,6 +276,10 @@ function psAlg(key) {
|
|
|
263
276
|
switch (key.algorithm.hash.name) {
|
|
264
277
|
case 'SHA-256':
|
|
265
278
|
return 'PS256';
|
|
279
|
+
case 'SHA-384':
|
|
280
|
+
return 'PS384';
|
|
281
|
+
case 'SHA-512':
|
|
282
|
+
return 'PS512';
|
|
266
283
|
default:
|
|
267
284
|
throw new UnsupportedOperationError('unsupported RsaHashedKeyAlgorithm hash name');
|
|
268
285
|
}
|
|
@@ -271,6 +288,10 @@ function rsAlg(key) {
|
|
|
271
288
|
switch (key.algorithm.hash.name) {
|
|
272
289
|
case 'SHA-256':
|
|
273
290
|
return 'RS256';
|
|
291
|
+
case 'SHA-384':
|
|
292
|
+
return 'RS384';
|
|
293
|
+
case 'SHA-512':
|
|
294
|
+
return 'RS512';
|
|
274
295
|
default:
|
|
275
296
|
throw new UnsupportedOperationError('unsupported RsaHashedKeyAlgorithm hash name');
|
|
276
297
|
}
|
|
@@ -279,11 +300,15 @@ function esAlg(key) {
|
|
|
279
300
|
switch (key.algorithm.namedCurve) {
|
|
280
301
|
case 'P-256':
|
|
281
302
|
return 'ES256';
|
|
303
|
+
case 'P-384':
|
|
304
|
+
return 'ES384';
|
|
305
|
+
case 'P-521':
|
|
306
|
+
return 'ES512';
|
|
282
307
|
default:
|
|
283
308
|
throw new UnsupportedOperationError('unsupported EcKeyAlgorithm namedCurve');
|
|
284
309
|
}
|
|
285
310
|
}
|
|
286
|
-
function
|
|
311
|
+
function keyToJws(key) {
|
|
287
312
|
switch (key.algorithm.name) {
|
|
288
313
|
case 'RSA-PSS':
|
|
289
314
|
return psAlg(key);
|
|
@@ -292,16 +317,30 @@ function determineJWSAlgorithm(key) {
|
|
|
292
317
|
case 'ECDSA':
|
|
293
318
|
return esAlg(key);
|
|
294
319
|
case 'Ed25519':
|
|
320
|
+
case 'Ed448':
|
|
295
321
|
return 'EdDSA';
|
|
296
322
|
default:
|
|
297
323
|
throw new UnsupportedOperationError('unsupported CryptoKey algorithm name');
|
|
298
324
|
}
|
|
299
325
|
}
|
|
326
|
+
function getClockSkew(client) {
|
|
327
|
+
if (Number.isFinite(client[clockSkew])) {
|
|
328
|
+
return client[clockSkew];
|
|
329
|
+
}
|
|
330
|
+
return 0;
|
|
331
|
+
}
|
|
332
|
+
function getClockTolerance(client) {
|
|
333
|
+
const tolerance = client[clockTolerance];
|
|
334
|
+
if (Number.isFinite(tolerance) && Math.sign(tolerance) !== -1) {
|
|
335
|
+
return tolerance;
|
|
336
|
+
}
|
|
337
|
+
return 30;
|
|
338
|
+
}
|
|
300
339
|
function epochTime() {
|
|
301
340
|
return Math.floor(Date.now() / 1000);
|
|
302
341
|
}
|
|
303
342
|
function clientAssertion(as, client) {
|
|
304
|
-
const now = epochTime();
|
|
343
|
+
const now = epochTime() + getClockSkew(client);
|
|
305
344
|
return {
|
|
306
345
|
jti: randomBytes(),
|
|
307
346
|
aud: [as.issuer, as.token_endpoint],
|
|
@@ -314,7 +353,7 @@ function clientAssertion(as, client) {
|
|
|
314
353
|
}
|
|
315
354
|
async function privateKeyJwt(as, client, key, kid) {
|
|
316
355
|
return jwt({
|
|
317
|
-
alg:
|
|
356
|
+
alg: keyToJws(key),
|
|
318
357
|
kid,
|
|
319
358
|
}, clientAssertion(as, client), key);
|
|
320
359
|
}
|
|
@@ -398,7 +437,7 @@ async function jwt(header, claimsSet, key) {
|
|
|
398
437
|
throw new TypeError('CryptoKey instances used for signing assertions must include "sign" in their "usages"');
|
|
399
438
|
}
|
|
400
439
|
const input = `${b64u(buf(JSON.stringify(header)))}.${b64u(buf(JSON.stringify(claimsSet)))}`;
|
|
401
|
-
const signature = b64u(await crypto.subtle.sign(
|
|
440
|
+
const signature = b64u(await crypto.subtle.sign(keyToSubtle(key), key, buf(input)));
|
|
402
441
|
return `${input}.${signature}`;
|
|
403
442
|
}
|
|
404
443
|
export async function issueRequestObject(as, client, parameters, privateKey) {
|
|
@@ -413,7 +452,7 @@ export async function issueRequestObject(as, client, parameters, privateKey) {
|
|
|
413
452
|
throw new TypeError('"privateKey.key" must be a private CryptoKey');
|
|
414
453
|
}
|
|
415
454
|
parameters.set('client_id', client.client_id);
|
|
416
|
-
const now = epochTime();
|
|
455
|
+
const now = epochTime() + getClockSkew(client);
|
|
417
456
|
const claims = {
|
|
418
457
|
...Object.fromEntries(parameters.entries()),
|
|
419
458
|
jti: randomBytes(),
|
|
@@ -445,12 +484,12 @@ export async function issueRequestObject(as, client, parameters, privateKey) {
|
|
|
445
484
|
}
|
|
446
485
|
}
|
|
447
486
|
return jwt({
|
|
448
|
-
alg:
|
|
487
|
+
alg: keyToJws(key),
|
|
449
488
|
typ: 'oauth-authz-req+jwt',
|
|
450
489
|
kid,
|
|
451
490
|
}, claims, key);
|
|
452
491
|
}
|
|
453
|
-
async function dpopProofJwt(headers, options, url, htm, accessToken) {
|
|
492
|
+
async function dpopProofJwt(headers, options, url, htm, clockSkew, accessToken) {
|
|
454
493
|
const { privateKey, publicKey, nonce = dpopNonces.get(url.origin) } = options;
|
|
455
494
|
if (!isPrivateKey(privateKey)) {
|
|
456
495
|
throw new TypeError('"DPoP.privateKey" must be a private CryptoKey');
|
|
@@ -464,9 +503,9 @@ async function dpopProofJwt(headers, options, url, htm, accessToken) {
|
|
|
464
503
|
if (!publicKey.extractable) {
|
|
465
504
|
throw new TypeError('"DPoP.publicKey.extractable" must be true');
|
|
466
505
|
}
|
|
467
|
-
const now = epochTime();
|
|
506
|
+
const now = epochTime() + clockSkew;
|
|
468
507
|
const proof = await jwt({
|
|
469
|
-
alg:
|
|
508
|
+
alg: keyToJws(privateKey),
|
|
470
509
|
typ: 'dpop+jwt',
|
|
471
510
|
jwk: await publicJwk(publicKey),
|
|
472
511
|
}, {
|
|
@@ -507,7 +546,7 @@ export async function pushedAuthorizationRequest(as, client, parameters, options
|
|
|
507
546
|
const headers = prepareHeaders(options?.headers);
|
|
508
547
|
headers.set('accept', 'application/json');
|
|
509
548
|
if (options?.DPoP !== undefined) {
|
|
510
|
-
await dpopProofJwt(headers, options.DPoP, url, 'POST');
|
|
549
|
+
await dpopProofJwt(headers, options.DPoP, url, 'POST', getClockSkew(client));
|
|
511
550
|
}
|
|
512
551
|
return authenticatedRequest(as, client, 'POST', url, body, headers, options);
|
|
513
552
|
}
|
|
@@ -620,7 +659,7 @@ export async function protectedResourceRequest(accessToken, method, url, headers
|
|
|
620
659
|
headers.set('authorization', `Bearer ${accessToken}`);
|
|
621
660
|
}
|
|
622
661
|
else {
|
|
623
|
-
await dpopProofJwt(headers, options.DPoP, url, 'GET', accessToken);
|
|
662
|
+
await dpopProofJwt(headers, options.DPoP, url, 'GET', getClockSkew({ [clockSkew]: options?.clockSkew }), accessToken);
|
|
624
663
|
headers.set('authorization', `DPoP ${accessToken}`);
|
|
625
664
|
}
|
|
626
665
|
return fetch(url.href, {
|
|
@@ -646,7 +685,10 @@ export async function userInfoRequest(as, client, accessToken, options) {
|
|
|
646
685
|
headers.set('accept', 'application/json');
|
|
647
686
|
headers.append('accept', 'application/jwt');
|
|
648
687
|
}
|
|
649
|
-
return protectedResourceRequest(accessToken, 'GET', url, headers, null,
|
|
688
|
+
return protectedResourceRequest(accessToken, 'GET', url, headers, null, {
|
|
689
|
+
...options,
|
|
690
|
+
clockSkew: getClockSkew(client),
|
|
691
|
+
});
|
|
650
692
|
}
|
|
651
693
|
let jwksCache;
|
|
652
694
|
async function getPublicSigKeyFromIssuerJwksUri(as, options, header) {
|
|
@@ -707,7 +749,9 @@ async function getPublicSigKeyFromIssuerJwksUri(as, options, header) {
|
|
|
707
749
|
}
|
|
708
750
|
switch (true) {
|
|
709
751
|
case alg === 'ES256' && jwk.crv !== 'P-256':
|
|
710
|
-
case alg === '
|
|
752
|
+
case alg === 'ES384' && jwk.crv !== 'P-384':
|
|
753
|
+
case alg === 'ES512' && jwk.crv !== 'P-521':
|
|
754
|
+
case alg === 'EdDSA' && !(jwk.crv === 'Ed25519' || jwk.crv === 'Ed448'):
|
|
711
755
|
return false;
|
|
712
756
|
}
|
|
713
757
|
return true;
|
|
@@ -745,7 +789,7 @@ export async function processUserInfoResponse(as, client, expectedSubject, respo
|
|
|
745
789
|
let json;
|
|
746
790
|
if (getContentType(response) === 'application/jwt') {
|
|
747
791
|
assertReadableResponse(response);
|
|
748
|
-
const { claims } = await validateJwt(await response.text(), checkSigningAlgorithm.bind(undefined, client.userinfo_signed_response_alg, as.userinfo_signing_alg_values_supported), noSignatureCheck)
|
|
792
|
+
const { claims } = await validateJwt(await response.text(), checkSigningAlgorithm.bind(undefined, client.userinfo_signed_response_alg, as.userinfo_signing_alg_values_supported), noSignatureCheck, getClockSkew(client), getClockTolerance(client))
|
|
749
793
|
.then(validateOptionalAudience.bind(undefined, client.client_id))
|
|
750
794
|
.then(validateOptionalIssuer.bind(undefined, as.issuer));
|
|
751
795
|
json = claims;
|
|
@@ -801,7 +845,7 @@ async function tokenEndpointRequest(as, client, grantType, parameters, options)
|
|
|
801
845
|
const headers = prepareHeaders(options?.headers);
|
|
802
846
|
headers.set('accept', 'application/json');
|
|
803
847
|
if (options?.DPoP !== undefined) {
|
|
804
|
-
await dpopProofJwt(headers, options.DPoP, url, 'POST');
|
|
848
|
+
await dpopProofJwt(headers, options.DPoP, url, 'POST', getClockSkew(client));
|
|
805
849
|
}
|
|
806
850
|
return authenticatedRequest(as, client, 'POST', url, parameters, headers, options);
|
|
807
851
|
}
|
|
@@ -873,7 +917,7 @@ async function processGenericAccessTokenResponse(as, client, response, ignoreIdT
|
|
|
873
917
|
throw new OPE('"response" body "id_token" property must be a non-empty string');
|
|
874
918
|
}
|
|
875
919
|
if (json.id_token) {
|
|
876
|
-
const { claims } = await validateJwt(json.id_token, checkSigningAlgorithm.bind(undefined, client.id_token_signed_response_alg, as.id_token_signing_alg_values_supported), noSignatureCheck)
|
|
920
|
+
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))
|
|
877
921
|
.then(validatePresence.bind(undefined, ['aud', 'exp', 'iat', 'iss', 'sub']))
|
|
878
922
|
.then(validateIssuer.bind(undefined, as.issuer))
|
|
879
923
|
.then(validateAudience.bind(undefined, client.client_id));
|
|
@@ -977,8 +1021,8 @@ export async function processAuthorizationCodeOpenIDResponse(as, client, respons
|
|
|
977
1021
|
if (typeof maxAge !== 'number' || maxAge < 0) {
|
|
978
1022
|
throw new TypeError('"options.max_age" must be a non-negative number');
|
|
979
1023
|
}
|
|
980
|
-
const now = epochTime();
|
|
981
|
-
const tolerance =
|
|
1024
|
+
const now = epochTime() + getClockSkew(client);
|
|
1025
|
+
const tolerance = getClockTolerance(client);
|
|
982
1026
|
if (claims.auth_time + maxAge < now - tolerance) {
|
|
983
1027
|
throw new OPE('too much time has elapsed since the last End-User authentication');
|
|
984
1028
|
}
|
|
@@ -1105,7 +1149,7 @@ export async function processIntrospectionResponse(as, client, response) {
|
|
|
1105
1149
|
let json;
|
|
1106
1150
|
if (getContentType(response) === 'application/token-introspection+jwt') {
|
|
1107
1151
|
assertReadableResponse(response);
|
|
1108
|
-
const { claims } = await validateJwt(await response.text(), checkSigningAlgorithm.bind(undefined, client.introspection_signed_response_alg, as.introspection_signing_alg_values_supported), noSignatureCheck)
|
|
1152
|
+
const { claims } = await validateJwt(await response.text(), checkSigningAlgorithm.bind(undefined, client.introspection_signed_response_alg, as.introspection_signing_alg_values_supported), noSignatureCheck, getClockSkew(client), getClockTolerance(client))
|
|
1109
1153
|
.then(checkJwtType.bind(undefined, 'token-introspection+jwt'))
|
|
1110
1154
|
.then(validatePresence.bind(undefined, ['aud', 'iat', 'iss']))
|
|
1111
1155
|
.then(validateIssuer.bind(undefined, as.issuer))
|
|
@@ -1210,26 +1254,50 @@ function checkRsaKeyAlgorithm(algorithm) {
|
|
|
1210
1254
|
throw new OPE(`${algorithm.name} modulusLength must be at least 2048 bits`);
|
|
1211
1255
|
}
|
|
1212
1256
|
}
|
|
1213
|
-
function
|
|
1257
|
+
function ecdsaHashName(namedCurve) {
|
|
1258
|
+
switch (namedCurve) {
|
|
1259
|
+
case 'P-256':
|
|
1260
|
+
return 'SHA-256';
|
|
1261
|
+
case 'P-384':
|
|
1262
|
+
return 'SHA-384';
|
|
1263
|
+
case 'P-521':
|
|
1264
|
+
return 'SHA-512';
|
|
1265
|
+
default:
|
|
1266
|
+
throw new UnsupportedOperationError();
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
function keyToSubtle(key) {
|
|
1214
1270
|
switch (key.algorithm.name) {
|
|
1215
1271
|
case 'ECDSA':
|
|
1216
|
-
return { name: key.algorithm.name, hash: { name: 'SHA-256' } };
|
|
1217
|
-
case 'RSA-PSS':
|
|
1218
|
-
checkRsaKeyAlgorithm(key.algorithm);
|
|
1219
1272
|
return {
|
|
1220
1273
|
name: key.algorithm.name,
|
|
1221
|
-
|
|
1274
|
+
hash: { name: ecdsaHashName(key.algorithm.namedCurve) },
|
|
1222
1275
|
};
|
|
1276
|
+
case 'RSA-PSS': {
|
|
1277
|
+
checkRsaKeyAlgorithm(key.algorithm);
|
|
1278
|
+
switch (key.algorithm.hash.name) {
|
|
1279
|
+
case 'SHA-256':
|
|
1280
|
+
case 'SHA-384':
|
|
1281
|
+
case 'SHA-512':
|
|
1282
|
+
return {
|
|
1283
|
+
name: key.algorithm.name,
|
|
1284
|
+
saltLength: parseInt(key.algorithm.hash.name.slice(-3), 10) >> 3,
|
|
1285
|
+
};
|
|
1286
|
+
default:
|
|
1287
|
+
throw new UnsupportedOperationError();
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1223
1290
|
case 'RSASSA-PKCS1-v1_5':
|
|
1224
1291
|
checkRsaKeyAlgorithm(key.algorithm);
|
|
1225
1292
|
return { name: key.algorithm.name };
|
|
1293
|
+
case 'Ed448':
|
|
1226
1294
|
case 'Ed25519':
|
|
1227
1295
|
return { name: key.algorithm.name };
|
|
1228
1296
|
}
|
|
1229
1297
|
throw new UnsupportedOperationError();
|
|
1230
1298
|
}
|
|
1231
1299
|
const noSignatureCheck = Symbol();
|
|
1232
|
-
async function validateJwt(jws, checkAlg, getKey) {
|
|
1300
|
+
async function validateJwt(jws, checkAlg, getKey, clockSkew, clockTolerance) {
|
|
1233
1301
|
const { 0: protectedHeader, 1: payload, 2: encodedSignature, length } = jws.split('.');
|
|
1234
1302
|
if (length === 5) {
|
|
1235
1303
|
throw new UnsupportedOperationError('JWE structure JWTs are not supported');
|
|
@@ -1255,7 +1323,7 @@ async function validateJwt(jws, checkAlg, getKey) {
|
|
|
1255
1323
|
if (getKey !== noSignatureCheck) {
|
|
1256
1324
|
const key = await getKey(header);
|
|
1257
1325
|
const input = `${protectedHeader}.${payload}`;
|
|
1258
|
-
const verified = await crypto.subtle.verify(
|
|
1326
|
+
const verified = await crypto.subtle.verify(keyToSubtle(key), key, signature, buf(input));
|
|
1259
1327
|
if (!verified) {
|
|
1260
1328
|
throw new OPE('JWT signature verification failed');
|
|
1261
1329
|
}
|
|
@@ -1270,13 +1338,12 @@ async function validateJwt(jws, checkAlg, getKey) {
|
|
|
1270
1338
|
if (!isJsonObject(claims)) {
|
|
1271
1339
|
throw new OPE('JWT Payload must be a top level object');
|
|
1272
1340
|
}
|
|
1273
|
-
const now = epochTime();
|
|
1274
|
-
const tolerance = 30;
|
|
1341
|
+
const now = epochTime() + clockSkew;
|
|
1275
1342
|
if (claims.exp !== undefined) {
|
|
1276
1343
|
if (typeof claims.exp !== 'number') {
|
|
1277
1344
|
throw new OPE('unexpected JWT "exp" (expiration time) claim type');
|
|
1278
1345
|
}
|
|
1279
|
-
if (claims.exp <= now -
|
|
1346
|
+
if (claims.exp <= now - clockTolerance) {
|
|
1280
1347
|
throw new OPE('unexpected JWT "exp" (expiration time) claim value, timestamp is <= now()');
|
|
1281
1348
|
}
|
|
1282
1349
|
}
|
|
@@ -1294,7 +1361,7 @@ async function validateJwt(jws, checkAlg, getKey) {
|
|
|
1294
1361
|
if (typeof claims.nbf !== 'number') {
|
|
1295
1362
|
throw new OPE('unexpected JWT "nbf" (not before) claim type');
|
|
1296
1363
|
}
|
|
1297
|
-
if (claims.nbf > now +
|
|
1364
|
+
if (claims.nbf > now + clockTolerance) {
|
|
1298
1365
|
throw new OPE('unexpected JWT "nbf" (not before) claim value, timestamp is > now()');
|
|
1299
1366
|
}
|
|
1300
1367
|
}
|
|
@@ -1321,7 +1388,7 @@ export async function validateJwtAuthResponse(as, client, parameters, expectedSt
|
|
|
1321
1388
|
if (typeof as.jwks_uri !== 'string') {
|
|
1322
1389
|
throw new TypeError('"as.jwks_uri" must be a string');
|
|
1323
1390
|
}
|
|
1324
|
-
const { claims } = await validateJwt(response, checkSigningAlgorithm.bind(undefined, client.authorization_signed_response_alg, as.authorization_signing_alg_values_supported), getPublicSigKeyFromIssuerJwksUri.bind(undefined, as, options))
|
|
1391
|
+
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))
|
|
1325
1392
|
.then(validatePresence.bind(undefined, ['aud', 'exp', 'iss']))
|
|
1326
1393
|
.then(validateIssuer.bind(undefined, as.issuer))
|
|
1327
1394
|
.then(validateAudience.bind(undefined, client.client_id));
|
|
@@ -1416,26 +1483,38 @@ export function validateAuthResponse(as, client, parameters, expectedState) {
|
|
|
1416
1483
|
}
|
|
1417
1484
|
return new CallbackParameters(parameters);
|
|
1418
1485
|
}
|
|
1419
|
-
|
|
1420
|
-
const { ext, key_ops, use, ...key } = jwk;
|
|
1421
|
-
let algorithm;
|
|
1486
|
+
function algToSubtle(alg, crv) {
|
|
1422
1487
|
switch (alg) {
|
|
1423
1488
|
case 'PS256':
|
|
1424
|
-
|
|
1425
|
-
|
|
1489
|
+
case 'PS384':
|
|
1490
|
+
case 'PS512':
|
|
1491
|
+
return { name: 'RSA-PSS', hash: { name: `SHA-${alg.slice(-3)}` } };
|
|
1426
1492
|
case 'RS256':
|
|
1427
|
-
|
|
1428
|
-
|
|
1493
|
+
case 'RS384':
|
|
1494
|
+
case 'RS512':
|
|
1495
|
+
return { name: 'RSASSA-PKCS1-v1_5', hash: { name: `SHA-${alg.slice(-3)}` } };
|
|
1429
1496
|
case 'ES256':
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
case '
|
|
1433
|
-
|
|
1434
|
-
|
|
1497
|
+
case 'ES384':
|
|
1498
|
+
return { name: 'ECDSA', namedCurve: `P-${alg.slice(-3)}` };
|
|
1499
|
+
case 'ES512':
|
|
1500
|
+
return { name: 'ECDSA', namedCurve: 'P-521' };
|
|
1501
|
+
case 'EdDSA': {
|
|
1502
|
+
switch (crv) {
|
|
1503
|
+
case 'Ed25519':
|
|
1504
|
+
return { name: 'Ed25519' };
|
|
1505
|
+
case 'Ed448':
|
|
1506
|
+
return { name: 'Ed448' };
|
|
1507
|
+
default:
|
|
1508
|
+
throw new UnsupportedOperationError();
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1435
1511
|
default:
|
|
1436
1512
|
throw new UnsupportedOperationError();
|
|
1437
1513
|
}
|
|
1438
|
-
|
|
1514
|
+
}
|
|
1515
|
+
async function importJwk(alg, jwk) {
|
|
1516
|
+
const { ext, key_ops, use, ...key } = jwk;
|
|
1517
|
+
return crypto.subtle.importKey('jwk', key, algToSubtle(alg, jwk.crv), true, ['verify']);
|
|
1439
1518
|
}
|
|
1440
1519
|
export async function deviceAuthorizationRequest(as, client, parameters, options) {
|
|
1441
1520
|
assertAs(as);
|
|
@@ -1512,35 +1591,15 @@ export async function processDeviceCodeResponse(as, client, response) {
|
|
|
1512
1591
|
return processGenericAccessTokenResponse(as, client, response);
|
|
1513
1592
|
}
|
|
1514
1593
|
export async function generateKeyPair(alg, options) {
|
|
1515
|
-
let algorithm;
|
|
1516
1594
|
if (!validateString(alg)) {
|
|
1517
1595
|
throw new TypeError('"alg" must be a non-empty string');
|
|
1518
1596
|
}
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
|
|
1526
|
-
};
|
|
1527
|
-
break;
|
|
1528
|
-
case 'RS256':
|
|
1529
|
-
algorithm = {
|
|
1530
|
-
name: 'RSASSA-PKCS1-v1_5',
|
|
1531
|
-
hash: { name: 'SHA-256' },
|
|
1532
|
-
modulusLength: options?.modulusLength ?? 2048,
|
|
1533
|
-
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
|
|
1534
|
-
};
|
|
1535
|
-
break;
|
|
1536
|
-
case 'ES256':
|
|
1537
|
-
algorithm = { name: 'ECDSA', namedCurve: 'P-256' };
|
|
1538
|
-
break;
|
|
1539
|
-
case 'EdDSA':
|
|
1540
|
-
algorithm = { name: 'Ed25519' };
|
|
1541
|
-
break;
|
|
1542
|
-
default:
|
|
1543
|
-
throw new UnsupportedOperationError();
|
|
1597
|
+
const algorithm = algToSubtle(alg, alg === 'EdDSA' ? options?.crv ?? 'Ed25519' : undefined);
|
|
1598
|
+
if (alg.startsWith('PS') || alg.startsWith('RS')) {
|
|
1599
|
+
Object.assign(algorithm, {
|
|
1600
|
+
modulusLength: options?.modulusLength ?? 2048,
|
|
1601
|
+
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
|
|
1602
|
+
});
|
|
1544
1603
|
}
|
|
1545
1604
|
return (crypto.subtle.generateKey(algorithm, options?.extractable ?? false, ['sign', 'verify']));
|
|
1546
1605
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oauth4webapi",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "OAuth 2 / OpenID Connect for Web Platform API JavaScript runtimes",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"auth",
|
|
@@ -62,21 +62,21 @@
|
|
|
62
62
|
"test": "bash -c 'source .node_flags.sh && ava'"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
|
-
"@esbuild-kit/esm-loader": "^2.5.
|
|
66
|
-
"@types/node": "^18.
|
|
67
|
-
"@types/qunit": "^2.19.
|
|
68
|
-
"ava": "^5.
|
|
69
|
-
"edge-runtime": "^2.
|
|
70
|
-
"esbuild": "^0.
|
|
71
|
-
"jose": "^4.
|
|
72
|
-
"patch-package": "^6.5.
|
|
73
|
-
"prettier": "^2.8.
|
|
65
|
+
"@esbuild-kit/esm-loader": "^2.5.5",
|
|
66
|
+
"@types/node": "^18.15.0",
|
|
67
|
+
"@types/qunit": "^2.19.4",
|
|
68
|
+
"ava": "^5.2.0",
|
|
69
|
+
"edge-runtime": "^2.1.2",
|
|
70
|
+
"esbuild": "^0.17.11",
|
|
71
|
+
"jose": "^4.13.1",
|
|
72
|
+
"patch-package": "^6.5.1",
|
|
73
|
+
"prettier": "^2.8.4",
|
|
74
74
|
"prettier-plugin-jsdoc": "^0.4.2",
|
|
75
|
-
"qunit": "^2.19.
|
|
75
|
+
"qunit": "^2.19.4",
|
|
76
76
|
"timekeeper": "^2.2.0",
|
|
77
|
-
"typedoc": "^0.23.
|
|
78
|
-
"typedoc-plugin-markdown": "^3.
|
|
79
|
-
"typescript": "^4.9.
|
|
80
|
-
"undici": "^5.
|
|
77
|
+
"typedoc": "^0.23.26",
|
|
78
|
+
"typedoc-plugin-markdown": "^3.14.0",
|
|
79
|
+
"typescript": "^4.9.5",
|
|
80
|
+
"undici": "^5.20.0"
|
|
81
81
|
}
|
|
82
82
|
}
|