jose 4.11.4 → 4.12.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/dist/browser/jwks/local.js +25 -10
- package/dist/browser/util/errors.js +1 -0
- package/dist/node/cjs/jwks/local.js +26 -11
- package/dist/node/cjs/util/errors.js +1 -0
- package/dist/node/esm/jwks/local.js +25 -10
- package/dist/node/esm/util/errors.js +1 -0
- package/dist/types/jwks/local.d.ts +41 -4
- package/dist/types/jwks/remote.d.ts +44 -6
- package/dist/types/jwt/sign.d.ts +1 -1
- package/dist/types/util/errors.d.ts +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ export class LocalJWKSet {
|
|
|
38
38
|
this._jwks = clone(jwks);
|
|
39
39
|
}
|
|
40
40
|
async getKey(protectedHeader, token) {
|
|
41
|
-
const { alg, kid } = { ...protectedHeader, ...token.header };
|
|
41
|
+
const { alg, kid } = { ...protectedHeader, ...token === null || token === void 0 ? void 0 : token.header };
|
|
42
42
|
const kty = getKtyFromAlg(alg);
|
|
43
43
|
const candidates = this._jwks.keys.filter((jwk) => {
|
|
44
44
|
let candidate = kty === jwk.kty;
|
|
@@ -80,18 +80,33 @@ export class LocalJWKSet {
|
|
|
80
80
|
throw new JWKSNoMatchingKey();
|
|
81
81
|
}
|
|
82
82
|
else if (length !== 1) {
|
|
83
|
-
|
|
83
|
+
const error = new JWKSMultipleMatchingKeys();
|
|
84
|
+
const { _cached } = this;
|
|
85
|
+
error[Symbol.asyncIterator] = async function* () {
|
|
86
|
+
for (const jwk of candidates) {
|
|
87
|
+
try {
|
|
88
|
+
yield await importWithAlgCache(_cached, jwk, alg);
|
|
89
|
+
}
|
|
90
|
+
catch (_a) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
throw error;
|
|
84
96
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
97
|
+
return importWithAlgCache(this._cached, jwk, alg);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function importWithAlgCache(cache, jwk, alg) {
|
|
101
|
+
const cached = cache.get(jwk) || cache.set(jwk, {}).get(jwk);
|
|
102
|
+
if (cached[alg] === undefined) {
|
|
103
|
+
const keyObject = await importJWK({ ...jwk, ext: true }, alg);
|
|
104
|
+
if (keyObject.type !== 'public') {
|
|
105
|
+
throw new JWKSInvalid('JSON Web Key Set members must be public keys');
|
|
92
106
|
}
|
|
93
|
-
|
|
107
|
+
cached[alg] = keyObject;
|
|
94
108
|
}
|
|
109
|
+
return cached[alg];
|
|
95
110
|
}
|
|
96
111
|
export function createLocalJWKSet(jwks) {
|
|
97
112
|
return LocalJWKSet.prototype.getKey.bind(new LocalJWKSet(jwks));
|
|
@@ -42,7 +42,7 @@ class LocalJWKSet {
|
|
|
42
42
|
this._jwks = clone(jwks);
|
|
43
43
|
}
|
|
44
44
|
async getKey(protectedHeader, token) {
|
|
45
|
-
const { alg, kid } = { ...protectedHeader, ...token.header };
|
|
45
|
+
const { alg, kid } = { ...protectedHeader, ...token === null || token === void 0 ? void 0 : token.header };
|
|
46
46
|
const kty = getKtyFromAlg(alg);
|
|
47
47
|
const candidates = this._jwks.keys.filter((jwk) => {
|
|
48
48
|
let candidate = kty === jwk.kty;
|
|
@@ -84,20 +84,35 @@ class LocalJWKSet {
|
|
|
84
84
|
throw new errors_js_1.JWKSNoMatchingKey();
|
|
85
85
|
}
|
|
86
86
|
else if (length !== 1) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
87
|
+
const error = new errors_js_1.JWKSMultipleMatchingKeys();
|
|
88
|
+
const { _cached } = this;
|
|
89
|
+
error[Symbol.asyncIterator] = async function* () {
|
|
90
|
+
for (const jwk of candidates) {
|
|
91
|
+
try {
|
|
92
|
+
yield await importWithAlgCache(_cached, jwk, alg);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
throw error;
|
|
96
100
|
}
|
|
97
|
-
return
|
|
101
|
+
return importWithAlgCache(this._cached, jwk, alg);
|
|
98
102
|
}
|
|
99
103
|
}
|
|
100
104
|
exports.LocalJWKSet = LocalJWKSet;
|
|
105
|
+
async function importWithAlgCache(cache, jwk, alg) {
|
|
106
|
+
const cached = cache.get(jwk) || cache.set(jwk, {}).get(jwk);
|
|
107
|
+
if (cached[alg] === undefined) {
|
|
108
|
+
const keyObject = await (0, import_js_1.importJWK)({ ...jwk, ext: true }, alg);
|
|
109
|
+
if (keyObject.type !== 'public') {
|
|
110
|
+
throw new errors_js_1.JWKSInvalid('JSON Web Key Set members must be public keys');
|
|
111
|
+
}
|
|
112
|
+
cached[alg] = keyObject;
|
|
113
|
+
}
|
|
114
|
+
return cached[alg];
|
|
115
|
+
}
|
|
101
116
|
function createLocalJWKSet(jwks) {
|
|
102
117
|
return LocalJWKSet.prototype.getKey.bind(new LocalJWKSet(jwks));
|
|
103
118
|
}
|
|
@@ -38,7 +38,7 @@ export class LocalJWKSet {
|
|
|
38
38
|
this._jwks = clone(jwks);
|
|
39
39
|
}
|
|
40
40
|
async getKey(protectedHeader, token) {
|
|
41
|
-
const { alg, kid } = { ...protectedHeader, ...token.header };
|
|
41
|
+
const { alg, kid } = { ...protectedHeader, ...token === null || token === void 0 ? void 0 : token.header };
|
|
42
42
|
const kty = getKtyFromAlg(alg);
|
|
43
43
|
const candidates = this._jwks.keys.filter((jwk) => {
|
|
44
44
|
let candidate = kty === jwk.kty;
|
|
@@ -80,18 +80,33 @@ export class LocalJWKSet {
|
|
|
80
80
|
throw new JWKSNoMatchingKey();
|
|
81
81
|
}
|
|
82
82
|
else if (length !== 1) {
|
|
83
|
-
|
|
83
|
+
const error = new JWKSMultipleMatchingKeys();
|
|
84
|
+
const { _cached } = this;
|
|
85
|
+
error[Symbol.asyncIterator] = async function* () {
|
|
86
|
+
for (const jwk of candidates) {
|
|
87
|
+
try {
|
|
88
|
+
yield await importWithAlgCache(_cached, jwk, alg);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
throw error;
|
|
84
96
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
97
|
+
return importWithAlgCache(this._cached, jwk, alg);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function importWithAlgCache(cache, jwk, alg) {
|
|
101
|
+
const cached = cache.get(jwk) || cache.set(jwk, {}).get(jwk);
|
|
102
|
+
if (cached[alg] === undefined) {
|
|
103
|
+
const keyObject = await importJWK({ ...jwk, ext: true }, alg);
|
|
104
|
+
if (keyObject.type !== 'public') {
|
|
105
|
+
throw new JWKSInvalid('JSON Web Key Set members must be public keys');
|
|
92
106
|
}
|
|
93
|
-
|
|
107
|
+
cached[alg] = keyObject;
|
|
94
108
|
}
|
|
109
|
+
return cached[alg];
|
|
95
110
|
}
|
|
96
111
|
export function createLocalJWKSet(jwks) {
|
|
97
112
|
return LocalJWKSet.prototype.getKey.bind(new LocalJWKSet(jwks));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { KeyLike, JWSHeaderParameters, JSONWebKeySet, FlattenedJWSInput
|
|
1
|
+
import type { KeyLike, JWSHeaderParameters, JSONWebKeySet, FlattenedJWSInput } from '../types';
|
|
2
2
|
/** @private */
|
|
3
3
|
export declare function isJWKSLike(jwks: unknown): jwks is JSONWebKeySet;
|
|
4
4
|
/** @private */
|
|
@@ -6,13 +6,20 @@ export declare class LocalJWKSet {
|
|
|
6
6
|
protected _jwks?: JSONWebKeySet;
|
|
7
7
|
private _cached;
|
|
8
8
|
constructor(jwks: unknown);
|
|
9
|
-
getKey(protectedHeader
|
|
9
|
+
getKey(protectedHeader?: JWSHeaderParameters, token?: FlattenedJWSInput): Promise<KeyLike>;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* Returns a function that resolves to a key object from a locally stored, or otherwise available,
|
|
13
13
|
* JSON Web Key Set.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
15
|
+
* It uses the "alg" (JWS Algorithm) Header Parameter to determine the right JWK "kty" (Key Type),
|
|
16
|
+
* then proceeds to match the JWK "kid" (Key ID) with one found in the JWS Header Parameters (if
|
|
17
|
+
* there is one) while also respecting the JWK "use" (Public Key Use) and JWK "key_ops" (Key
|
|
18
|
+
* Operations) Parameters (if they are present on the JWK).
|
|
19
|
+
*
|
|
20
|
+
* Only a single public key must match the selection process. As shown in the example below when
|
|
21
|
+
* multiple keys get matched it is possible to opt-in to iterate over the matched keys and attempt
|
|
22
|
+
* verification in an iterative manner.
|
|
16
23
|
*
|
|
17
24
|
* @example Usage
|
|
18
25
|
*
|
|
@@ -43,6 +50,36 @@ export declare class LocalJWKSet {
|
|
|
43
50
|
* console.log(payload)
|
|
44
51
|
* ```
|
|
45
52
|
*
|
|
53
|
+
* @example Opting-in to multiple JWKS matches using `createLocalJWKSet`
|
|
54
|
+
*
|
|
55
|
+
* ```js
|
|
56
|
+
* const options = {
|
|
57
|
+
* issuer: 'urn:example:issuer',
|
|
58
|
+
* audience: 'urn:example:audience',
|
|
59
|
+
* }
|
|
60
|
+
* const { payload, protectedHeader } = await jose
|
|
61
|
+
* .jwtVerify(jwt, JWKS, options)
|
|
62
|
+
* .catch(async (error) => {
|
|
63
|
+
* if (error?.code === 'ERR_JWKS_MULTIPLE_MATCHING_KEYS') {
|
|
64
|
+
* for await (const publicKey of error) {
|
|
65
|
+
* try {
|
|
66
|
+
* return await jose.jwtVerify(jwt, publicKey, options)
|
|
67
|
+
* } catch (innerError) {
|
|
68
|
+
* if (innerError?.code === 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED') {
|
|
69
|
+
* continue
|
|
70
|
+
* }
|
|
71
|
+
* throw innerError
|
|
72
|
+
* }
|
|
73
|
+
* }
|
|
74
|
+
* throw new jose.errors.JWSSignatureVerificationFailed()
|
|
75
|
+
* }
|
|
76
|
+
*
|
|
77
|
+
* throw error
|
|
78
|
+
* })
|
|
79
|
+
* console.log(protectedHeader)
|
|
80
|
+
* console.log(payload)
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
46
83
|
* @param jwks JSON Web Key Set formatted object.
|
|
47
84
|
*/
|
|
48
|
-
export declare function createLocalJWKSet(jwks: JSONWebKeySet):
|
|
85
|
+
export declare function createLocalJWKSet(jwks: JSONWebKeySet): (protectedHeader?: JWSHeaderParameters | undefined, token?: FlattenedJWSInput | undefined) => Promise<KeyLike>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { JWSHeaderParameters, FlattenedJWSInput
|
|
1
|
+
import type { KeyLike, JWSHeaderParameters, FlattenedJWSInput } from '../types';
|
|
2
2
|
/** Options for the remote JSON Web Key Set. */
|
|
3
3
|
export interface RemoteJWKSetOptions {
|
|
4
4
|
/**
|
|
@@ -30,10 +30,18 @@ export interface RemoteJWKSetOptions {
|
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
32
32
|
* Returns a function that resolves to a key object downloaded from a remote endpoint returning a
|
|
33
|
-
* JSON Web Key Set, that is, for example, an OAuth 2.0 or OIDC jwks_uri.
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
33
|
+
* JSON Web Key Set, that is, for example, an OAuth 2.0 or OIDC jwks_uri. The JSON Web Key Set is
|
|
34
|
+
* fetched when no key matches the selection process but only as frequently as the
|
|
35
|
+
* `cooldownDuration` option allows to prevent abuse.
|
|
36
|
+
*
|
|
37
|
+
* It uses the "alg" (JWS Algorithm) Header Parameter to determine the right JWK "kty" (Key Type),
|
|
38
|
+
* then proceeds to match the JWK "kid" (Key ID) with one found in the JWS Header Parameters (if
|
|
39
|
+
* there is one) while also respecting the JWK "use" (Public Key Use) and JWK "key_ops" (Key
|
|
40
|
+
* Operations) Parameters (if they are present on the JWK).
|
|
41
|
+
*
|
|
42
|
+
* Only a single public key must match the selection process. As shown in the example below when
|
|
43
|
+
* multiple keys get matched it is possible to opt-in to iterate over the matched keys and attempt
|
|
44
|
+
* verification in an iterative manner.
|
|
37
45
|
*
|
|
38
46
|
* @example Usage
|
|
39
47
|
*
|
|
@@ -48,7 +56,37 @@ export interface RemoteJWKSetOptions {
|
|
|
48
56
|
* console.log(payload)
|
|
49
57
|
* ```
|
|
50
58
|
*
|
|
59
|
+
* @example Opting-in to multiple JWKS matches using `createRemoteJWKSet`
|
|
60
|
+
*
|
|
61
|
+
* ```js
|
|
62
|
+
* const options = {
|
|
63
|
+
* issuer: 'urn:example:issuer',
|
|
64
|
+
* audience: 'urn:example:audience',
|
|
65
|
+
* }
|
|
66
|
+
* const { payload, protectedHeader } = await jose
|
|
67
|
+
* .jwtVerify(jwt, JWKS, options)
|
|
68
|
+
* .catch(async (error) => {
|
|
69
|
+
* if (error?.code === 'ERR_JWKS_MULTIPLE_MATCHING_KEYS') {
|
|
70
|
+
* for await (const publicKey of error) {
|
|
71
|
+
* try {
|
|
72
|
+
* return await jose.jwtVerify(jwt, publicKey, options)
|
|
73
|
+
* } catch (innerError) {
|
|
74
|
+
* if (innerError?.code === 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED') {
|
|
75
|
+
* continue
|
|
76
|
+
* }
|
|
77
|
+
* throw innerError
|
|
78
|
+
* }
|
|
79
|
+
* }
|
|
80
|
+
* throw new jose.errors.JWSSignatureVerificationFailed()
|
|
81
|
+
* }
|
|
82
|
+
*
|
|
83
|
+
* throw error
|
|
84
|
+
* })
|
|
85
|
+
* console.log(protectedHeader)
|
|
86
|
+
* console.log(payload)
|
|
87
|
+
* ```
|
|
88
|
+
*
|
|
51
89
|
* @param url URL to fetch the JSON Web Key Set from.
|
|
52
90
|
* @param options Options for the remote JSON Web Key Set.
|
|
53
91
|
*/
|
|
54
|
-
export declare function createRemoteJWKSet(url: URL, options?: RemoteJWKSetOptions):
|
|
92
|
+
export declare function createRemoteJWKSet(url: URL, options?: RemoteJWKSetOptions): (protectedHeader?: JWSHeaderParameters | undefined, token?: FlattenedJWSInput | undefined) => Promise<KeyLike>;
|
package/dist/types/jwt/sign.d.ts
CHANGED
|
@@ -53,7 +53,7 @@ import { ProduceJWT } from './produce';
|
|
|
53
53
|
* 59lLWBCsocvvbd1cvDMNlRywAAyhsCb1SuX4nEAK9mrSBdfmoF2Nm3eilfsOds0f
|
|
54
54
|
* K5mX069IKG82CMqh3Mzptd7e7lyb9lsoGO0BAtjho3cWtha/UZ70vfaMzGuZ6JmQ
|
|
55
55
|
* ak6k+8+UFd93M4z0Qo74OhXB
|
|
56
|
-
* -----END PRIVATE KEY
|
|
56
|
+
* -----END PRIVATE KEY-----`
|
|
57
57
|
* const privateKey = await jose.importPKCS8(pkcs8, alg)
|
|
58
58
|
*
|
|
59
59
|
* const jwt = await new jose.SignJWT({ 'urn:example:claim': true })
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { KeyLike } from '../types';
|
|
1
2
|
/** A generic Error subclass that all other specific JOSE Error subclasses inherit from. */
|
|
2
3
|
export declare class JOSEError extends Error {
|
|
3
4
|
/** A unique error code for the particular error subclass. */
|
|
@@ -78,6 +79,8 @@ export declare class JWKSNoMatchingKey extends JOSEError {
|
|
|
78
79
|
}
|
|
79
80
|
/** An error subclass thrown when multiple keys match from a JWKS. */
|
|
80
81
|
export declare class JWKSMultipleMatchingKeys extends JOSEError {
|
|
82
|
+
/** @ignore */
|
|
83
|
+
[Symbol.asyncIterator]: () => AsyncIterableIterator<KeyLike>;
|
|
81
84
|
static get code(): 'ERR_JWKS_MULTIPLE_MATCHING_KEYS';
|
|
82
85
|
code: string;
|
|
83
86
|
message: string;
|
package/package.json
CHANGED