@cfworker/jwt 4.0.6 → 5.1.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/parse.d.ts +8 -1
- package/dist/parse.js +67 -23
- package/dist/types.d.ts +8 -1
- package/package.json +11 -11
- package/src/declarations.d.ts +6 -0
- package/src/parse.ts +108 -29
- package/src/types.ts +8 -0
- package/tsconfig.json +2 -8
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ const jwt = request.headers.get('Authorization');
|
|
|
15
15
|
const issuer = '...'; // Auth0 origin.
|
|
16
16
|
const audience = '...'; // Auth0 client id.
|
|
17
17
|
|
|
18
|
-
const result = await parseJwt(jwt, issuer, audience);
|
|
18
|
+
const result = await parseJwt({ jwt, issuer, audience });
|
|
19
19
|
if (!result.valid) {
|
|
20
20
|
console.log(result.reason); // Invalid issuer/audience, expired, etc
|
|
21
21
|
} else {
|
package/dist/parse.d.ts
CHANGED
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
import { DecodedJwt, JwtParseResult } from './types.js';
|
|
2
|
-
export
|
|
2
|
+
export interface JwtParseOptions {
|
|
3
|
+
jwt: string;
|
|
4
|
+
issuer: string | string[];
|
|
5
|
+
audience: string;
|
|
6
|
+
resolveKey?: (decoded: DecodedJwt) => Promise<CryptoKey | null>;
|
|
7
|
+
skewMs?: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function parseJwt({ jwt, issuer, audience, resolveKey, skewMs }: JwtParseOptions): Promise<JwtParseResult>;
|
package/dist/parse.js
CHANGED
|
@@ -2,40 +2,52 @@ import { algs, algToHash } from './algs.js';
|
|
|
2
2
|
import { decodeJwt } from './decode.js';
|
|
3
3
|
import { getKey } from './jwks.js';
|
|
4
4
|
import { verifyJwtSignature } from './verify.js';
|
|
5
|
-
const
|
|
6
|
-
export async function parseJwt(
|
|
5
|
+
const defaultSkewMs = 60 * 1000;
|
|
6
|
+
export async function parseJwt({ jwt, issuer, audience, resolveKey = getKey, skewMs = defaultSkewMs }) {
|
|
7
7
|
let decoded;
|
|
8
8
|
try {
|
|
9
|
-
decoded = decodeJwt(
|
|
9
|
+
decoded = decodeJwt(jwt);
|
|
10
10
|
}
|
|
11
11
|
catch {
|
|
12
|
-
return {
|
|
12
|
+
return {
|
|
13
|
+
valid: false,
|
|
14
|
+
reason: `Unable to decode JWT.`,
|
|
15
|
+
reasonCode: 0
|
|
16
|
+
};
|
|
13
17
|
}
|
|
14
18
|
const { typ, alg } = decoded.header;
|
|
15
19
|
if (typeof typ !== 'undefined' && typ !== 'JWT') {
|
|
16
20
|
return {
|
|
17
21
|
valid: false,
|
|
18
|
-
reason: `Invalid JWT type ${JSON.stringify(typ)}. Expected "JWT"
|
|
22
|
+
reason: `Invalid JWT type ${JSON.stringify(typ)}. Expected "JWT".`,
|
|
23
|
+
reasonCode: 0,
|
|
24
|
+
decoded
|
|
19
25
|
};
|
|
20
26
|
}
|
|
21
27
|
if (!algToHash[alg]) {
|
|
22
28
|
return {
|
|
23
29
|
valid: false,
|
|
24
|
-
reason: `Invalid JWT algorithm ${JSON.stringify(alg)}. Supported: ${algs}
|
|
30
|
+
reason: `Invalid JWT algorithm ${JSON.stringify(alg)}. Supported: ${algs}.`,
|
|
31
|
+
reasonCode: 0,
|
|
32
|
+
decoded
|
|
25
33
|
};
|
|
26
34
|
}
|
|
27
35
|
const { sub, aud, iss, iat, exp, nbf } = decoded.payload;
|
|
28
36
|
if (typeof sub !== 'string') {
|
|
29
37
|
return {
|
|
30
38
|
valid: false,
|
|
31
|
-
reason: `Subject claim (sub) is required and must be a string. Received ${JSON.stringify(sub)}
|
|
39
|
+
reason: `Subject claim (sub) is required and must be a string. Received ${JSON.stringify(sub)}.`,
|
|
40
|
+
reasonCode: 0,
|
|
41
|
+
decoded
|
|
32
42
|
};
|
|
33
43
|
}
|
|
34
44
|
if (typeof aud === 'string') {
|
|
35
45
|
if (aud !== audience) {
|
|
36
46
|
return {
|
|
37
47
|
valid: false,
|
|
38
|
-
reason: `Invalid JWT audience claim (aud) ${JSON.stringify(aud)}. Expected "${audience}"
|
|
48
|
+
reason: `Invalid JWT audience claim (aud) ${JSON.stringify(aud)}. Expected "${audience}".`,
|
|
49
|
+
reasonCode: 0,
|
|
50
|
+
decoded
|
|
39
51
|
};
|
|
40
52
|
}
|
|
41
53
|
}
|
|
@@ -45,26 +57,34 @@ export async function parseJwt(encodedToken, issuer, audience, resolveKey = getK
|
|
|
45
57
|
if (!aud.includes(audience)) {
|
|
46
58
|
return {
|
|
47
59
|
valid: false,
|
|
48
|
-
reason: `Invalid JWT audience claim array (aud) ${JSON.stringify(aud)}. Does not include "${audience}"
|
|
60
|
+
reason: `Invalid JWT audience claim array (aud) ${JSON.stringify(aud)}. Does not include "${audience}".`,
|
|
61
|
+
reasonCode: 0,
|
|
62
|
+
decoded
|
|
49
63
|
};
|
|
50
64
|
}
|
|
51
65
|
}
|
|
52
66
|
else {
|
|
53
67
|
return {
|
|
54
68
|
valid: false,
|
|
55
|
-
reason: `Invalid JWT audience claim (aud) ${JSON.stringify(aud)}. Expected a string or a non-empty array of strings
|
|
69
|
+
reason: `Invalid JWT audience claim (aud) ${JSON.stringify(aud)}. Expected a string or a non-empty array of strings.`,
|
|
70
|
+
reasonCode: 0,
|
|
71
|
+
decoded
|
|
56
72
|
};
|
|
57
73
|
}
|
|
58
74
|
if (!(iss === issuer || (Array.isArray(issuer) && issuer.includes(iss)))) {
|
|
59
75
|
return {
|
|
60
76
|
valid: false,
|
|
61
|
-
reason: `Invalid JWT issuer claim (iss) ${JSON.stringify(decoded.payload.iss)}. Expected ${JSON.stringify(issuer)}
|
|
77
|
+
reason: `Invalid JWT issuer claim (iss) ${JSON.stringify(decoded.payload.iss)}. Expected ${JSON.stringify(issuer)}.`,
|
|
78
|
+
reasonCode: 0,
|
|
79
|
+
decoded
|
|
62
80
|
};
|
|
63
81
|
}
|
|
64
82
|
if (typeof exp !== 'number') {
|
|
65
83
|
return {
|
|
66
84
|
valid: false,
|
|
67
|
-
reason: `Invalid JWT expiry date claim (exp) ${JSON.stringify(exp)}. Expected number
|
|
85
|
+
reason: `Invalid JWT expiry date claim (exp) ${JSON.stringify(exp)}. Expected number.`,
|
|
86
|
+
reasonCode: 0,
|
|
87
|
+
decoded
|
|
68
88
|
};
|
|
69
89
|
}
|
|
70
90
|
const currentDate = new Date(Date.now());
|
|
@@ -74,14 +94,18 @@ export async function parseJwt(encodedToken, issuer, audience, resolveKey = getK
|
|
|
74
94
|
if (expired) {
|
|
75
95
|
return {
|
|
76
96
|
valid: false,
|
|
77
|
-
reason: `JWT is expired. Expiry date: ${expiryDate}; Current date: ${currentDate}
|
|
97
|
+
reason: `JWT is expired. Expiry date: ${expiryDate}; Current date: ${currentDate};`,
|
|
98
|
+
reasonCode: 1,
|
|
99
|
+
decoded
|
|
78
100
|
};
|
|
79
101
|
}
|
|
80
102
|
if (nbf !== undefined) {
|
|
81
103
|
if (typeof nbf !== 'number') {
|
|
82
104
|
return {
|
|
83
105
|
valid: false,
|
|
84
|
-
reason: `Invalid JWT not before date claim (nbf) ${JSON.stringify(nbf)}. Expected number
|
|
106
|
+
reason: `Invalid JWT not before date claim (nbf) ${JSON.stringify(nbf)}. Expected number.`,
|
|
107
|
+
reasonCode: 0,
|
|
108
|
+
decoded
|
|
85
109
|
};
|
|
86
110
|
}
|
|
87
111
|
const notBeforeDate = new Date(0);
|
|
@@ -90,7 +114,9 @@ export async function parseJwt(encodedToken, issuer, audience, resolveKey = getK
|
|
|
90
114
|
if (early) {
|
|
91
115
|
return {
|
|
92
116
|
valid: false,
|
|
93
|
-
reason: `JWT cannot be used prior to not before date claim (nbf). Not before date: ${notBeforeDate}; Current date: ${currentDate}
|
|
117
|
+
reason: `JWT cannot be used prior to not before date claim (nbf). Not before date: ${notBeforeDate}; Current date: ${currentDate};`,
|
|
118
|
+
reasonCode: 0,
|
|
119
|
+
decoded
|
|
94
120
|
};
|
|
95
121
|
}
|
|
96
122
|
}
|
|
@@ -98,7 +124,9 @@ export async function parseJwt(encodedToken, issuer, audience, resolveKey = getK
|
|
|
98
124
|
if (typeof iat !== 'number') {
|
|
99
125
|
return {
|
|
100
126
|
valid: false,
|
|
101
|
-
reason: `Invalid JWT issued at date claim (iat) ${JSON.stringify(iat)}. Expected number
|
|
127
|
+
reason: `Invalid JWT issued at date claim (iat) ${JSON.stringify(iat)}. Expected number.`,
|
|
128
|
+
reasonCode: 0,
|
|
129
|
+
decoded
|
|
102
130
|
};
|
|
103
131
|
}
|
|
104
132
|
const issuedAtDate = new Date(0);
|
|
@@ -107,7 +135,9 @@ export async function parseJwt(encodedToken, issuer, audience, resolveKey = getK
|
|
|
107
135
|
if (postIssued) {
|
|
108
136
|
return {
|
|
109
137
|
valid: false,
|
|
110
|
-
reason: `JWT issued at date claim (iat) is in the future. Issued at date: ${issuedAtDate}; Current date: ${currentDate}
|
|
138
|
+
reason: `JWT issued at date claim (iat) is in the future. Issued at date: ${issuedAtDate}; Current date: ${currentDate};`,
|
|
139
|
+
reasonCode: 0,
|
|
140
|
+
decoded
|
|
111
141
|
};
|
|
112
142
|
}
|
|
113
143
|
}
|
|
@@ -118,13 +148,17 @@ export async function parseJwt(encodedToken, issuer, audience, resolveKey = getK
|
|
|
118
148
|
catch (e) {
|
|
119
149
|
return {
|
|
120
150
|
valid: false,
|
|
121
|
-
reason: `Error retrieving public key to verify JWT signature: ${e instanceof Error ? e.message : e}
|
|
151
|
+
reason: `Error retrieving public key to verify JWT signature: ${e instanceof Error ? e.message : e}`,
|
|
152
|
+
reasonCode: 0,
|
|
153
|
+
decoded
|
|
122
154
|
};
|
|
123
155
|
}
|
|
124
156
|
if (!key) {
|
|
125
157
|
return {
|
|
126
158
|
valid: false,
|
|
127
|
-
reason: `Unable to resolve public key to verify JWT signature
|
|
159
|
+
reason: `Unable to resolve public key to verify JWT signature.`,
|
|
160
|
+
reasonCode: 0,
|
|
161
|
+
decoded
|
|
128
162
|
};
|
|
129
163
|
}
|
|
130
164
|
let signatureValid;
|
|
@@ -132,11 +166,21 @@ export async function parseJwt(encodedToken, issuer, audience, resolveKey = getK
|
|
|
132
166
|
signatureValid = await verifyJwtSignature(decoded, key);
|
|
133
167
|
}
|
|
134
168
|
catch {
|
|
135
|
-
return {
|
|
169
|
+
return {
|
|
170
|
+
valid: false,
|
|
171
|
+
reason: `Error verifying JWT signature.`,
|
|
172
|
+
reasonCode: 0,
|
|
173
|
+
decoded
|
|
174
|
+
};
|
|
136
175
|
}
|
|
137
176
|
if (!signatureValid) {
|
|
138
|
-
return {
|
|
177
|
+
return {
|
|
178
|
+
valid: false,
|
|
179
|
+
reason: `JWT signature is invalid.`,
|
|
180
|
+
reasonCode: 0,
|
|
181
|
+
decoded
|
|
182
|
+
};
|
|
139
183
|
}
|
|
140
|
-
const payload = decoded
|
|
141
|
-
return { valid: true, payload };
|
|
184
|
+
const { header, payload } = decoded;
|
|
185
|
+
return { valid: true, header, payload };
|
|
142
186
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -29,11 +29,18 @@ export interface JsonWebKeyset {
|
|
|
29
29
|
}
|
|
30
30
|
export interface ValidJwtParseResult {
|
|
31
31
|
valid: true;
|
|
32
|
+
header: JwtHeader;
|
|
32
33
|
payload: JwtPayload;
|
|
33
34
|
}
|
|
34
35
|
export interface InvalidJwtParseResult {
|
|
35
36
|
valid: false;
|
|
36
37
|
reason: string;
|
|
38
|
+
reasonCode: InvalidJwtReasonCode;
|
|
39
|
+
decoded?: DecodedJwt;
|
|
40
|
+
}
|
|
41
|
+
export declare const enum InvalidJwtReasonCode {
|
|
42
|
+
Other = 0,
|
|
43
|
+
Expired = 1
|
|
37
44
|
}
|
|
38
45
|
export interface IssuerMetadata {
|
|
39
46
|
issuer: string;
|
|
@@ -61,4 +68,4 @@ export interface MtlsEndpointAliases {
|
|
|
61
68
|
introspection_endpoint?: string;
|
|
62
69
|
device_authorization_endpoint?: string;
|
|
63
70
|
}
|
|
64
|
-
export
|
|
71
|
+
export type JwtParseResult = ValidJwtParseResult | InvalidJwtParseResult;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cfworker/jwt",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"description": "Tiny lib for decoding JWTs and verifying signatures, using native crypto APIs",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jwt",
|
|
@@ -31,19 +31,19 @@
|
|
|
31
31
|
"scripts": {
|
|
32
32
|
"build": "tsc --build",
|
|
33
33
|
"clean": "tsc --build --clean",
|
|
34
|
-
"
|
|
34
|
+
"pretest": "esbuild test/test.ts --target=esnext --bundle --format=esm --conditions=worker,browser --outdir=dist-test --ignore-annotations",
|
|
35
|
+
"test": "node ../../test.mjs"
|
|
35
36
|
},
|
|
36
37
|
"dependencies": {
|
|
37
|
-
"
|
|
38
|
-
"@cloudflare/workers-types": "^2.2.2",
|
|
39
|
-
"rfc4648": "^1.5.2"
|
|
38
|
+
"rfc4648": "^1.5.3"
|
|
40
39
|
},
|
|
41
40
|
"devDependencies": {
|
|
42
|
-
"@
|
|
43
|
-
"@types/
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"mocha": "^10.
|
|
47
|
-
"typescript": "^
|
|
41
|
+
"@types/chai": "^4.3.17",
|
|
42
|
+
"@types/mocha": "^10.0.7",
|
|
43
|
+
"chai": "^5.1.1",
|
|
44
|
+
"esbuild": "^0.23.1",
|
|
45
|
+
"mocha": "^10.7.3",
|
|
46
|
+
"typescript": "^5.5.4",
|
|
47
|
+
"wrangler": "^3.72.0"
|
|
48
48
|
}
|
|
49
49
|
}
|
package/src/parse.ts
CHANGED
|
@@ -1,31 +1,72 @@
|
|
|
1
1
|
import { algs, algToHash } from './algs.js';
|
|
2
2
|
import { decodeJwt } from './decode.js';
|
|
3
3
|
import { getKey } from './jwks.js';
|
|
4
|
-
import { DecodedJwt, JwtParseResult } from './types.js';
|
|
4
|
+
import { DecodedJwt, InvalidJwtReasonCode, JwtParseResult } from './types.js';
|
|
5
5
|
import { verifyJwtSignature } from './verify.js';
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const defaultSkewMs = 60 * 1000;
|
|
8
|
+
|
|
9
|
+
export interface JwtParseOptions {
|
|
10
|
+
/**
|
|
11
|
+
* The JWT to parse.
|
|
12
|
+
*/
|
|
13
|
+
jwt: string;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Permitted issuer(s) of the JWT. Used to validate the iss claim.
|
|
17
|
+
* Note: if you're using the default resolveKey function, OIDC discovery will be used
|
|
18
|
+
* to fetch the public key(s) for the issuer.
|
|
19
|
+
*/
|
|
20
|
+
issuer: string | string[];
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Expected audience of the JWT.
|
|
24
|
+
*/
|
|
25
|
+
audience: string;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Given a decoded (but not fully verified) JWT, resolve the public key that
|
|
29
|
+
* should be used to verify the signature. Typical implementations will use
|
|
30
|
+
* the "kid" claim to look up the key in a JWKS endpoint or other key store.
|
|
31
|
+
* If the key cannot be resolved, the function should return null.
|
|
32
|
+
* @param decoded The decoded (but not fully verified / unsafe) JWT.
|
|
33
|
+
*/
|
|
34
|
+
resolveKey?: (decoded: DecodedJwt) => Promise<CryptoKey | null>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The maximum skew in milliseconds to allow when validating iat, nbf, and exp claims.
|
|
38
|
+
* Defaults to 60 seconds.
|
|
39
|
+
*/
|
|
40
|
+
skewMs?: number;
|
|
41
|
+
}
|
|
8
42
|
|
|
9
43
|
/**
|
|
10
44
|
* Parse a JWT.
|
|
11
45
|
*/
|
|
12
|
-
export async function parseJwt(
|
|
13
|
-
|
|
14
|
-
issuer
|
|
15
|
-
audience
|
|
16
|
-
resolveKey
|
|
17
|
-
|
|
46
|
+
export async function parseJwt({
|
|
47
|
+
jwt,
|
|
48
|
+
issuer,
|
|
49
|
+
audience,
|
|
50
|
+
resolveKey = getKey,
|
|
51
|
+
skewMs = defaultSkewMs
|
|
52
|
+
}: JwtParseOptions): Promise<JwtParseResult> {
|
|
18
53
|
let decoded: DecodedJwt;
|
|
19
54
|
try {
|
|
20
|
-
decoded = decodeJwt(
|
|
55
|
+
decoded = decodeJwt(jwt);
|
|
21
56
|
} catch {
|
|
22
|
-
return {
|
|
57
|
+
return {
|
|
58
|
+
valid: false,
|
|
59
|
+
reason: `Unable to decode JWT.`,
|
|
60
|
+
reasonCode: InvalidJwtReasonCode.Other
|
|
61
|
+
};
|
|
23
62
|
}
|
|
24
63
|
const { typ, alg } = decoded.header;
|
|
25
64
|
if (typeof typ !== 'undefined' && typ !== 'JWT') {
|
|
26
65
|
return {
|
|
27
66
|
valid: false,
|
|
28
|
-
reason: `Invalid JWT type ${JSON.stringify(typ)}. Expected "JWT"
|
|
67
|
+
reason: `Invalid JWT type ${JSON.stringify(typ)}. Expected "JWT".`,
|
|
68
|
+
reasonCode: InvalidJwtReasonCode.Other,
|
|
69
|
+
decoded
|
|
29
70
|
};
|
|
30
71
|
}
|
|
31
72
|
if (!algToHash[alg]) {
|
|
@@ -33,7 +74,9 @@ export async function parseJwt(
|
|
|
33
74
|
valid: false,
|
|
34
75
|
reason: `Invalid JWT algorithm ${JSON.stringify(
|
|
35
76
|
alg
|
|
36
|
-
)}. Supported: ${algs}
|
|
77
|
+
)}. Supported: ${algs}.`,
|
|
78
|
+
reasonCode: InvalidJwtReasonCode.Other,
|
|
79
|
+
decoded
|
|
37
80
|
};
|
|
38
81
|
}
|
|
39
82
|
|
|
@@ -43,7 +86,9 @@ export async function parseJwt(
|
|
|
43
86
|
valid: false,
|
|
44
87
|
reason: `Subject claim (sub) is required and must be a string. Received ${JSON.stringify(
|
|
45
88
|
sub
|
|
46
|
-
)}
|
|
89
|
+
)}.`,
|
|
90
|
+
reasonCode: InvalidJwtReasonCode.Other,
|
|
91
|
+
decoded
|
|
47
92
|
};
|
|
48
93
|
}
|
|
49
94
|
|
|
@@ -53,7 +98,9 @@ export async function parseJwt(
|
|
|
53
98
|
valid: false,
|
|
54
99
|
reason: `Invalid JWT audience claim (aud) ${JSON.stringify(
|
|
55
100
|
aud
|
|
56
|
-
)}. Expected "${audience}"
|
|
101
|
+
)}. Expected "${audience}".`,
|
|
102
|
+
reasonCode: InvalidJwtReasonCode.Other,
|
|
103
|
+
decoded
|
|
57
104
|
};
|
|
58
105
|
}
|
|
59
106
|
} else if (
|
|
@@ -66,7 +113,9 @@ export async function parseJwt(
|
|
|
66
113
|
valid: false,
|
|
67
114
|
reason: `Invalid JWT audience claim array (aud) ${JSON.stringify(
|
|
68
115
|
aud
|
|
69
|
-
)}. Does not include "${audience}"
|
|
116
|
+
)}. Does not include "${audience}".`,
|
|
117
|
+
reasonCode: InvalidJwtReasonCode.Other,
|
|
118
|
+
decoded
|
|
70
119
|
};
|
|
71
120
|
}
|
|
72
121
|
} else {
|
|
@@ -74,7 +123,9 @@ export async function parseJwt(
|
|
|
74
123
|
valid: false,
|
|
75
124
|
reason: `Invalid JWT audience claim (aud) ${JSON.stringify(
|
|
76
125
|
aud
|
|
77
|
-
)}. Expected a string or a non-empty array of strings
|
|
126
|
+
)}. Expected a string or a non-empty array of strings.`,
|
|
127
|
+
reasonCode: InvalidJwtReasonCode.Other,
|
|
128
|
+
decoded
|
|
78
129
|
};
|
|
79
130
|
}
|
|
80
131
|
|
|
@@ -83,7 +134,9 @@ export async function parseJwt(
|
|
|
83
134
|
valid: false,
|
|
84
135
|
reason: `Invalid JWT issuer claim (iss) ${JSON.stringify(
|
|
85
136
|
decoded.payload.iss
|
|
86
|
-
)}. Expected ${JSON.stringify(issuer)}
|
|
137
|
+
)}. Expected ${JSON.stringify(issuer)}.`,
|
|
138
|
+
reasonCode: InvalidJwtReasonCode.Other,
|
|
139
|
+
decoded
|
|
87
140
|
};
|
|
88
141
|
}
|
|
89
142
|
|
|
@@ -92,7 +145,9 @@ export async function parseJwt(
|
|
|
92
145
|
valid: false,
|
|
93
146
|
reason: `Invalid JWT expiry date claim (exp) ${JSON.stringify(
|
|
94
147
|
exp
|
|
95
|
-
)}. Expected number
|
|
148
|
+
)}. Expected number.`,
|
|
149
|
+
reasonCode: InvalidJwtReasonCode.Other,
|
|
150
|
+
decoded
|
|
96
151
|
};
|
|
97
152
|
}
|
|
98
153
|
const currentDate = new Date(Date.now());
|
|
@@ -102,7 +157,9 @@ export async function parseJwt(
|
|
|
102
157
|
if (expired) {
|
|
103
158
|
return {
|
|
104
159
|
valid: false,
|
|
105
|
-
reason: `JWT is expired. Expiry date: ${expiryDate}; Current date: ${currentDate}
|
|
160
|
+
reason: `JWT is expired. Expiry date: ${expiryDate}; Current date: ${currentDate};`,
|
|
161
|
+
reasonCode: InvalidJwtReasonCode.Expired,
|
|
162
|
+
decoded
|
|
106
163
|
};
|
|
107
164
|
}
|
|
108
165
|
|
|
@@ -112,7 +169,9 @@ export async function parseJwt(
|
|
|
112
169
|
valid: false,
|
|
113
170
|
reason: `Invalid JWT not before date claim (nbf) ${JSON.stringify(
|
|
114
171
|
nbf
|
|
115
|
-
)}. Expected number
|
|
172
|
+
)}. Expected number.`,
|
|
173
|
+
reasonCode: InvalidJwtReasonCode.Other,
|
|
174
|
+
decoded
|
|
116
175
|
};
|
|
117
176
|
}
|
|
118
177
|
const notBeforeDate = new Date(0);
|
|
@@ -121,7 +180,9 @@ export async function parseJwt(
|
|
|
121
180
|
if (early) {
|
|
122
181
|
return {
|
|
123
182
|
valid: false,
|
|
124
|
-
reason: `JWT cannot be used prior to not before date claim (nbf). Not before date: ${notBeforeDate}; Current date: ${currentDate}
|
|
183
|
+
reason: `JWT cannot be used prior to not before date claim (nbf). Not before date: ${notBeforeDate}; Current date: ${currentDate};`,
|
|
184
|
+
reasonCode: InvalidJwtReasonCode.Other,
|
|
185
|
+
decoded
|
|
125
186
|
};
|
|
126
187
|
}
|
|
127
188
|
}
|
|
@@ -132,7 +193,9 @@ export async function parseJwt(
|
|
|
132
193
|
valid: false,
|
|
133
194
|
reason: `Invalid JWT issued at date claim (iat) ${JSON.stringify(
|
|
134
195
|
iat
|
|
135
|
-
)}. Expected number
|
|
196
|
+
)}. Expected number.`,
|
|
197
|
+
reasonCode: InvalidJwtReasonCode.Other,
|
|
198
|
+
decoded
|
|
136
199
|
};
|
|
137
200
|
}
|
|
138
201
|
const issuedAtDate = new Date(0);
|
|
@@ -141,7 +204,9 @@ export async function parseJwt(
|
|
|
141
204
|
if (postIssued) {
|
|
142
205
|
return {
|
|
143
206
|
valid: false,
|
|
144
|
-
reason: `JWT issued at date claim (iat) is in the future. Issued at date: ${issuedAtDate}; Current date: ${currentDate}
|
|
207
|
+
reason: `JWT issued at date claim (iat) is in the future. Issued at date: ${issuedAtDate}; Current date: ${currentDate};`,
|
|
208
|
+
reasonCode: InvalidJwtReasonCode.Other,
|
|
209
|
+
decoded
|
|
145
210
|
};
|
|
146
211
|
}
|
|
147
212
|
}
|
|
@@ -154,24 +219,38 @@ export async function parseJwt(
|
|
|
154
219
|
valid: false,
|
|
155
220
|
reason: `Error retrieving public key to verify JWT signature: ${
|
|
156
221
|
e instanceof Error ? e.message : e
|
|
157
|
-
}
|
|
222
|
+
}`,
|
|
223
|
+
reasonCode: InvalidJwtReasonCode.Other,
|
|
224
|
+
decoded
|
|
158
225
|
};
|
|
159
226
|
}
|
|
160
227
|
if (!key) {
|
|
161
228
|
return {
|
|
162
229
|
valid: false,
|
|
163
|
-
reason: `Unable to resolve public key to verify JWT signature
|
|
230
|
+
reason: `Unable to resolve public key to verify JWT signature.`,
|
|
231
|
+
reasonCode: InvalidJwtReasonCode.Other,
|
|
232
|
+
decoded
|
|
164
233
|
};
|
|
165
234
|
}
|
|
166
235
|
let signatureValid: boolean;
|
|
167
236
|
try {
|
|
168
237
|
signatureValid = await verifyJwtSignature(decoded, key);
|
|
169
238
|
} catch {
|
|
170
|
-
return {
|
|
239
|
+
return {
|
|
240
|
+
valid: false,
|
|
241
|
+
reason: `Error verifying JWT signature.`,
|
|
242
|
+
reasonCode: InvalidJwtReasonCode.Other,
|
|
243
|
+
decoded
|
|
244
|
+
};
|
|
171
245
|
}
|
|
172
246
|
if (!signatureValid) {
|
|
173
|
-
return {
|
|
247
|
+
return {
|
|
248
|
+
valid: false,
|
|
249
|
+
reason: `JWT signature is invalid.`,
|
|
250
|
+
reasonCode: InvalidJwtReasonCode.Other,
|
|
251
|
+
decoded
|
|
252
|
+
};
|
|
174
253
|
}
|
|
175
|
-
const payload = decoded
|
|
176
|
-
return { valid: true, payload };
|
|
254
|
+
const { header, payload } = decoded;
|
|
255
|
+
return { valid: true, header, payload };
|
|
177
256
|
}
|
package/src/types.ts
CHANGED
|
@@ -41,12 +41,20 @@ export interface JsonWebKeyset {
|
|
|
41
41
|
|
|
42
42
|
export interface ValidJwtParseResult {
|
|
43
43
|
valid: true;
|
|
44
|
+
header: JwtHeader;
|
|
44
45
|
payload: JwtPayload;
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
export interface InvalidJwtParseResult {
|
|
48
49
|
valid: false;
|
|
49
50
|
reason: string;
|
|
51
|
+
reasonCode: InvalidJwtReasonCode;
|
|
52
|
+
decoded?: DecodedJwt;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const enum InvalidJwtReasonCode {
|
|
56
|
+
Other = 0,
|
|
57
|
+
Expired = 1
|
|
50
58
|
}
|
|
51
59
|
|
|
52
60
|
export interface IssuerMetadata {
|
package/tsconfig.json
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"extends": "../../tsconfig-base",
|
|
3
3
|
"compilerOptions": {
|
|
4
|
-
"target": "
|
|
4
|
+
"target": "ESNext",
|
|
5
5
|
"lib": ["ESNext", "WebWorker", "Webworker.Iterable"],
|
|
6
|
-
"types": [
|
|
7
|
-
"chai",
|
|
8
|
-
"mocha",
|
|
9
|
-
"@cfworker/worker-types",
|
|
10
|
-
"@cloudflare/workers-types"
|
|
11
|
-
],
|
|
12
6
|
"outDir": "dist",
|
|
13
7
|
"rootDir": "./src"
|
|
14
8
|
},
|
|
15
9
|
"include": ["src"],
|
|
16
|
-
"references": [
|
|
10
|
+
"references": []
|
|
17
11
|
}
|