@cfworker/jwt 3.0.0 → 4.0.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/dist/discovery.d.ts +2 -0
- package/dist/discovery.js +12 -0
- package/dist/jwks.js +15 -7
- package/dist/parse.js +2 -2
- package/dist/types.d.ts +26 -0
- package/package.json +1 -1
- package/src/discovery.ts +21 -0
- package/src/jwks.ts +27 -7
- package/src/parse.ts +4 -2
- package/src/types.ts +28 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export async function getIssuerMetadata(issuer) {
|
|
2
|
+
const url = new URL(issuer);
|
|
3
|
+
if (!url.pathname.endsWith('/')) {
|
|
4
|
+
url.pathname += '/';
|
|
5
|
+
}
|
|
6
|
+
url.pathname += '.well-known/openid-configuration';
|
|
7
|
+
const response = await fetch(url.href);
|
|
8
|
+
if (!response.ok) {
|
|
9
|
+
throw new Error(`Error loading OpenID discovery document at ${url.href}. ${response.status} ${response.statusText}`);
|
|
10
|
+
}
|
|
11
|
+
return response.json();
|
|
12
|
+
}
|
package/dist/jwks.js
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { algToHash } from './algs.js';
|
|
2
|
+
import { getIssuerMetadata } from './discovery.js';
|
|
2
3
|
export async function getJwks(issuer) {
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
const issuerMetadata = await getIssuerMetadata(issuer);
|
|
5
|
+
let url;
|
|
6
|
+
if (issuerMetadata.jwks_uri) {
|
|
7
|
+
url = new URL(issuerMetadata.jwks_uri);
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
10
|
+
url = new URL(issuer);
|
|
11
|
+
if (!url.pathname.endsWith('/')) {
|
|
12
|
+
url.pathname += '/';
|
|
13
|
+
}
|
|
14
|
+
url.pathname += '.well-known/jwks.json';
|
|
6
15
|
}
|
|
7
|
-
url.pathname += '.well-known/jwks.json';
|
|
8
16
|
const response = await fetch(url.href);
|
|
9
17
|
if (!response.ok) {
|
|
10
18
|
throw new Error(`Error loading jwks at ${url.href}. ${response.status} ${response.statusText}`);
|
|
@@ -14,11 +22,11 @@ export async function getJwks(issuer) {
|
|
|
14
22
|
const importedKeys = {};
|
|
15
23
|
export async function importKey(iss, jwk) {
|
|
16
24
|
if (jwk.kty !== 'RSA') {
|
|
17
|
-
throw new Error(`Unsupported jwk key type (kty) "${jwk.kty}"
|
|
25
|
+
throw new Error(`Unsupported jwk key type (kty) "${jwk.kty}": Full JWK was ${JSON.stringify(jwk)}`);
|
|
18
26
|
}
|
|
19
|
-
const hash = jwk.alg ? algToHash[jwk.alg] :
|
|
27
|
+
const hash = jwk.alg ? algToHash[jwk.alg] : 'SHA-256';
|
|
20
28
|
if (!hash) {
|
|
21
|
-
throw new Error(`Unsupported jwk Algorithm (alg) "${jwk.alg}"
|
|
29
|
+
throw new Error(`Unsupported jwk Algorithm (alg) "${jwk.alg}": Full JWK was ${JSON.stringify(jwk)}`);
|
|
22
30
|
}
|
|
23
31
|
const key = await crypto.subtle.importKey('jwk', jwk, { name: 'RSASSA-PKCS1-v1_5', hash }, false, ['verify']);
|
|
24
32
|
importedKeys[iss] = importedKeys[iss] || {};
|
package/dist/parse.js
CHANGED
|
@@ -115,10 +115,10 @@ export async function parseJwt(encodedToken, issuer, audience, resolveKey = getK
|
|
|
115
115
|
try {
|
|
116
116
|
key = await resolveKey(decoded);
|
|
117
117
|
}
|
|
118
|
-
catch {
|
|
118
|
+
catch (e) {
|
|
119
119
|
return {
|
|
120
120
|
valid: false,
|
|
121
|
-
reason: `Error retrieving public key to verify JWT signature
|
|
121
|
+
reason: `Error retrieving public key to verify JWT signature: ${e instanceof Error ? e.message : e}`
|
|
122
122
|
};
|
|
123
123
|
}
|
|
124
124
|
if (!key) {
|
package/dist/types.d.ts
CHANGED
|
@@ -34,4 +34,30 @@ export interface InvalidJwtParseResult {
|
|
|
34
34
|
valid: false;
|
|
35
35
|
reason: string;
|
|
36
36
|
}
|
|
37
|
+
export interface IssuerMetadata {
|
|
38
|
+
issuer: string;
|
|
39
|
+
authorization_endpoint?: string;
|
|
40
|
+
token_endpoint?: string;
|
|
41
|
+
jwks_uri?: string;
|
|
42
|
+
userinfo_endpoint?: string;
|
|
43
|
+
revocation_endpoint?: string;
|
|
44
|
+
end_session_endpoint?: string;
|
|
45
|
+
registration_endpoint?: string;
|
|
46
|
+
token_endpoint_auth_methods_supported?: string[];
|
|
47
|
+
token_endpoint_auth_signing_alg_values_supported?: string[];
|
|
48
|
+
introspection_endpoint_auth_methods_supported?: string[];
|
|
49
|
+
introspection_endpoint_auth_signing_alg_values_supported?: string[];
|
|
50
|
+
revocation_endpoint_auth_methods_supported?: string[];
|
|
51
|
+
revocation_endpoint_auth_signing_alg_values_supported?: string[];
|
|
52
|
+
request_object_signing_alg_values_supported?: string[];
|
|
53
|
+
mtls_endpoint_aliases?: MtlsEndpointAliases;
|
|
54
|
+
[key: string]: unknown;
|
|
55
|
+
}
|
|
56
|
+
export interface MtlsEndpointAliases {
|
|
57
|
+
token_endpoint?: string;
|
|
58
|
+
userinfo_endpoint?: string;
|
|
59
|
+
revocation_endpoint?: string;
|
|
60
|
+
introspection_endpoint?: string;
|
|
61
|
+
device_authorization_endpoint?: string;
|
|
62
|
+
}
|
|
37
63
|
export declare type JwtParseResult = ValidJwtParseResult | InvalidJwtParseResult;
|
package/package.json
CHANGED
package/src/discovery.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { IssuerMetadata } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Fetch an oidc discovery document
|
|
5
|
+
*/
|
|
6
|
+
export async function getIssuerMetadata(
|
|
7
|
+
issuer: string
|
|
8
|
+
): Promise<IssuerMetadata> {
|
|
9
|
+
const url = new URL(issuer);
|
|
10
|
+
if (!url.pathname.endsWith('/')) {
|
|
11
|
+
url.pathname += '/';
|
|
12
|
+
}
|
|
13
|
+
url.pathname += '.well-known/openid-configuration';
|
|
14
|
+
const response = await fetch(url.href);
|
|
15
|
+
if (!response.ok) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`Error loading OpenID discovery document at ${url.href}. ${response.status} ${response.statusText}`
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
return response.json();
|
|
21
|
+
}
|
package/src/jwks.ts
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
import { algToHash } from './algs.js';
|
|
2
|
+
import { getIssuerMetadata } from './discovery.js';
|
|
2
3
|
import { DecodedJwt, JsonWebKeyset } from './types.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Fetch a json web keyset.
|
|
6
7
|
*/
|
|
7
8
|
export async function getJwks(issuer: string): Promise<JsonWebKeyset> {
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
const issuerMetadata = await getIssuerMetadata(issuer);
|
|
10
|
+
|
|
11
|
+
let url;
|
|
12
|
+
if (issuerMetadata.jwks_uri) {
|
|
13
|
+
url = new URL(issuerMetadata.jwks_uri);
|
|
14
|
+
} else {
|
|
15
|
+
url = new URL(issuer);
|
|
16
|
+
if (!url.pathname.endsWith('/')) {
|
|
17
|
+
url.pathname += '/';
|
|
18
|
+
}
|
|
19
|
+
url.pathname += '.well-known/jwks.json';
|
|
11
20
|
}
|
|
12
|
-
|
|
21
|
+
|
|
13
22
|
const response = await fetch(url.href);
|
|
14
23
|
if (!response.ok) {
|
|
15
24
|
throw new Error(
|
|
@@ -28,11 +37,22 @@ const importedKeys: Record<string, Record<string, CryptoKey>> = {};
|
|
|
28
37
|
*/
|
|
29
38
|
export async function importKey(iss: string, jwk: JsonWebKey) {
|
|
30
39
|
if (jwk.kty !== 'RSA') {
|
|
31
|
-
throw new Error(
|
|
40
|
+
throw new Error(
|
|
41
|
+
`Unsupported jwk key type (kty) "${
|
|
42
|
+
jwk.kty
|
|
43
|
+
}": Full JWK was ${JSON.stringify(jwk)}`
|
|
44
|
+
);
|
|
32
45
|
}
|
|
33
|
-
|
|
46
|
+
// alg is not mandatory in a JWK but is available in the JWT, for now we default to
|
|
47
|
+
// SHA-256 (RS256) because this is the most common but evaluating the key length
|
|
48
|
+
// of the JWKS is probably an ideal way of identifying what the alg is.
|
|
49
|
+
const hash = jwk.alg ? algToHash[jwk.alg] : 'SHA-256';
|
|
34
50
|
if (!hash) {
|
|
35
|
-
throw new Error(
|
|
51
|
+
throw new Error(
|
|
52
|
+
`Unsupported jwk Algorithm (alg) "${
|
|
53
|
+
jwk.alg
|
|
54
|
+
}": Full JWK was ${JSON.stringify(jwk)}`
|
|
55
|
+
);
|
|
36
56
|
}
|
|
37
57
|
const key = await crypto.subtle.importKey(
|
|
38
58
|
'jwk',
|
package/src/parse.ts
CHANGED
|
@@ -149,10 +149,12 @@ export async function parseJwt(
|
|
|
149
149
|
let key: CryptoKey | null;
|
|
150
150
|
try {
|
|
151
151
|
key = await resolveKey(decoded);
|
|
152
|
-
} catch {
|
|
152
|
+
} catch (e) {
|
|
153
153
|
return {
|
|
154
154
|
valid: false,
|
|
155
|
-
reason: `Error retrieving public key to verify JWT signature
|
|
155
|
+
reason: `Error retrieving public key to verify JWT signature: ${
|
|
156
|
+
e instanceof Error ? e.message : e
|
|
157
|
+
}`
|
|
156
158
|
};
|
|
157
159
|
}
|
|
158
160
|
if (!key) {
|
package/src/types.ts
CHANGED
|
@@ -48,4 +48,32 @@ export interface InvalidJwtParseResult {
|
|
|
48
48
|
reason: string;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
export interface IssuerMetadata {
|
|
52
|
+
issuer: string;
|
|
53
|
+
authorization_endpoint?: string;
|
|
54
|
+
token_endpoint?: string;
|
|
55
|
+
jwks_uri?: string;
|
|
56
|
+
userinfo_endpoint?: string;
|
|
57
|
+
revocation_endpoint?: string;
|
|
58
|
+
end_session_endpoint?: string;
|
|
59
|
+
registration_endpoint?: string;
|
|
60
|
+
token_endpoint_auth_methods_supported?: string[];
|
|
61
|
+
token_endpoint_auth_signing_alg_values_supported?: string[];
|
|
62
|
+
introspection_endpoint_auth_methods_supported?: string[];
|
|
63
|
+
introspection_endpoint_auth_signing_alg_values_supported?: string[];
|
|
64
|
+
revocation_endpoint_auth_methods_supported?: string[];
|
|
65
|
+
revocation_endpoint_auth_signing_alg_values_supported?: string[];
|
|
66
|
+
request_object_signing_alg_values_supported?: string[];
|
|
67
|
+
mtls_endpoint_aliases?: MtlsEndpointAliases;
|
|
68
|
+
[key: string]: unknown;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface MtlsEndpointAliases {
|
|
72
|
+
token_endpoint?: string;
|
|
73
|
+
userinfo_endpoint?: string;
|
|
74
|
+
revocation_endpoint?: string;
|
|
75
|
+
introspection_endpoint?: string;
|
|
76
|
+
device_authorization_endpoint?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
51
79
|
export type JwtParseResult = ValidJwtParseResult | InvalidJwtParseResult;
|