Visitar URL original
feat: expose JWT's payload in JWTClaimValidationFailed instances · panva/jose@58bcffb · GitHub
Skip to content

Commit 58bcffb

Browse files
committed
feat: expose JWT's payload in JWTClaimValidationFailed instances
closes #680
1 parent cc2b2d7 commit 58bcffb

5 files changed

Lines changed: 77 additions & 20 deletions

File tree

‎docs/classes/util_errors.JWTClaimValidationFailed.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ if (err instanceof jose.errors.JWTClaimValidationFailed) {
3838

3939
- [claim](util_errors.JWTClaimValidationFailed.md#claim)
4040
- [code](util_errors.JWTClaimValidationFailed.md#code)
41+
- [payload](util_errors.JWTClaimValidationFailed.md#payload)
4142
- [reason](util_errors.JWTClaimValidationFailed.md#reason)
4243

4344
## Properties
@@ -58,6 +59,14 @@ A unique error code for this particular error subclass.
5859

5960
___
6061

62+
### payload
63+
64+
• **payload**: [`JWTPayload`](../interfaces/types.JWTPayload.md)
65+
66+
The parsed JWT payload.
67+
68+
___
69+
6170
### reason
6271

6372
• **reason**: `string`

‎docs/classes/util_errors.JWTExpired.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ if (err instanceof jose.errors.JWTExpired) {
3838

3939
- [claim](util_errors.JWTExpired.md#claim)
4040
- [code](util_errors.JWTExpired.md#code)
41+
- [payload](util_errors.JWTExpired.md#payload)
4142
- [reason](util_errors.JWTExpired.md#reason)
4243

4344
## Properties
@@ -58,6 +59,14 @@ A unique error code for this particular error subclass.
5859

5960
___
6061

62+
### payload
63+
64+
• **payload**: [`JWTPayload`](../interfaces/types.JWTPayload.md)
65+
66+
The parsed JWT payload.
67+
68+
___
69+
6170
### reason
6271

6372
• **reason**: `string`

‎src/jwt/decrypt.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export async function jwtDecrypt(
7777
if (protectedHeader.iss !== undefined && protectedHeader.iss !== payload.iss) {
7878
throw new JWTClaimValidationFailed(
7979
'replicated "iss" claim header parameter mismatch',
80+
payload,
8081
'iss',
8182
'mismatch',
8283
)
@@ -85,6 +86,7 @@ export async function jwtDecrypt(
8586
if (protectedHeader.sub !== undefined && protectedHeader.sub !== payload.sub) {
8687
throw new JWTClaimValidationFailed(
8788
'replicated "sub" claim header parameter mismatch',
89+
payload,
8890
'sub',
8991
'mismatch',
9092
)
@@ -96,6 +98,7 @@ export async function jwtDecrypt(
9698
) {
9799
throw new JWTClaimValidationFailed(
98100
'replicated "aud" claim header parameter mismatch',
101+
payload,
99102
'aud',
100103
'mismatch',
101104
)

‎src/lib/jwt_claims_set.ts‎

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,6 @@ export default (
3131
encodedPayload: Uint8Array,
3232
options: JWTClaimVerificationOptions = {},
3333
) => {
34-
const { typ } = options
35-
if (
36-
typ &&
37-
(typeof protectedHeader!.typ !== 'string' ||
38-
normalizeTyp(protectedHeader!.typ) !== normalizeTyp(typ))
39-
) {
40-
throw new JWTClaimValidationFailed('unexpected "typ" JWT header value', 'typ', 'check_failed')
41-
}
42-
4334
let payload!: { [propName: string]: unknown }
4435
try {
4536
payload = JSON.parse(decoder.decode(encodedPayload))
@@ -51,6 +42,20 @@ export default (
5142
throw new JWTInvalid('JWT Claims Set must be a top-level JSON object')
5243
}
5344

45+
const { typ } = options
46+
if (
47+
typ &&
48+
(typeof protectedHeader!.typ !== 'string' ||
49+
normalizeTyp(protectedHeader!.typ) !== normalizeTyp(typ))
50+
) {
51+
throw new JWTClaimValidationFailed(
52+
'unexpected "typ" JWT header value',
53+
payload,
54+
'typ',
55+
'check_failed',
56+
)
57+
}
58+
5459
const { requiredClaims = [], issuer, subject, audience, maxTokenAge } = options
5560

5661
const presenceCheck = [...requiredClaims]
@@ -62,23 +67,43 @@ export default (
6267

6368
for (const claim of new Set(presenceCheck.reverse())) {
6469
if (!(claim in payload)) {
65-
throw new JWTClaimValidationFailed(`missing required "${claim}" claim`, claim, 'missing')
70+
throw new JWTClaimValidationFailed(
71+
`missing required "${claim}" claim`,
72+
payload,
73+
claim,
74+
'missing',
75+
)
6676
}
6777
}
6878

6979
if (issuer && !(<unknown[]>(Array.isArray(issuer) ? issuer : [issuer])).includes(payload.iss!)) {
70-
throw new JWTClaimValidationFailed('unexpected "iss" claim value', 'iss', 'check_failed')
80+
throw new JWTClaimValidationFailed(
81+
'unexpected "iss" claim value',
82+
payload,
83+
'iss',
84+
'check_failed',
85+
)
7186
}
7287

7388
if (subject && payload.sub !== subject) {
74-
throw new JWTClaimValidationFailed('unexpected "sub" claim value', 'sub', 'check_failed')
89+
throw new JWTClaimValidationFailed(
90+
'unexpected "sub" claim value',
91+
payload,
92+
'sub',
93+
'check_failed',
94+
)
7595
}
7696

7797
if (
7898
audience &&
7999
!checkAudiencePresence(payload.aud, typeof audience === 'string' ? [audience] : audience)
80100
) {
81-
throw new JWTClaimValidationFailed('unexpected "aud" claim value', 'aud', 'check_failed')
101+
throw new JWTClaimValidationFailed(
102+
'unexpected "aud" claim value',
103+
payload,
104+
'aud',
105+
'check_failed',
106+
)
82107
}
83108

84109
let tolerance: number
@@ -100,16 +125,17 @@ export default (
100125
const now = epoch(currentDate || new Date())
101126

102127
if ((payload.iat !== undefined || maxTokenAge) && typeof payload.iat !== 'number') {
103-
throw new JWTClaimValidationFailed('"iat" claim must be a number', 'iat', 'invalid')
128+
throw new JWTClaimValidationFailed('"iat" claim must be a number', payload, 'iat', 'invalid')
104129
}
105130

106131
if (payload.nbf !== undefined) {
107132
if (typeof payload.nbf !== 'number') {
108-
throw new JWTClaimValidationFailed('"nbf" claim must be a number', 'nbf', 'invalid')
133+
throw new JWTClaimValidationFailed('"nbf" claim must be a number', payload, 'nbf', 'invalid')
109134
}
110135
if (payload.nbf > now + tolerance) {
111136
throw new JWTClaimValidationFailed(
112137
'"nbf" claim timestamp check failed',
138+
payload,
113139
'nbf',
114140
'check_failed',
115141
)
@@ -118,10 +144,10 @@ export default (
118144

119145
if (payload.exp !== undefined) {
120146
if (typeof payload.exp !== 'number') {
121-
throw new JWTClaimValidationFailed('"exp" claim must be a number', 'exp', 'invalid')
147+
throw new JWTClaimValidationFailed('"exp" claim must be a number', payload, 'exp', 'invalid')
122148
}
123149
if (payload.exp <= now - tolerance) {
124-
throw new JWTExpired('"exp" claim timestamp check failed', 'exp', 'check_failed')
150+
throw new JWTExpired('"exp" claim timestamp check failed', payload, 'exp', 'check_failed')
125151
}
126152
}
127153

@@ -132,6 +158,7 @@ export default (
132158
if (age - tolerance > max) {
133159
throw new JWTExpired(
134160
'"iat" claim timestamp check failed (too far in the past)',
161+
payload,
135162
'iat',
136163
'check_failed',
137164
)
@@ -140,6 +167,7 @@ export default (
140167
if (age < 0 - tolerance) {
141168
throw new JWTClaimValidationFailed(
142169
'"iat" claim timestamp check failed (it should be in the past)',
170+
payload,
143171
'iat',
144172
'check_failed',
145173
)

‎src/util/errors.ts‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { KeyLike } from '../types.d'
1+
import type { JWTPayload, KeyLike } from '../types.d'
22

33
/**
44
* A generic Error that all other JOSE specific Error subclasses extend.
@@ -72,11 +72,15 @@ export class JWTClaimValidationFailed extends JOSEError {
7272
/** Reason code for the validation failure. */
7373
reason: string
7474

75+
/** The parsed JWT payload. */
76+
payload: JWTPayload
77+
7578
/** @ignore */
76-
constructor(message: string, claim = 'unspecified', reason = 'unspecified') {
79+
constructor(message: string, payload: JWTPayload, claim = 'unspecified', reason = 'unspecified') {
7780
super(message)
7881
this.claim = claim
7982
this.reason = reason
83+
this.payload = payload
8084
}
8185
}
8286

@@ -117,11 +121,15 @@ export class JWTExpired extends JOSEError implements JWTClaimValidationFailed {
117121
/** Reason code for the validation failure. */
118122
reason: string
119123

124+
/** The parsed JWT payload. */
125+
payload: JWTPayload
126+
120127
/** @ignore */
121-
constructor(message: string, claim = 'unspecified', reason = 'unspecified') {
128+
constructor(message: string, payload: JWTPayload, claim = 'unspecified', reason = 'unspecified') {
122129
super(message)
123130
this.claim = claim
124131
this.reason = reason
132+
this.payload = payload
125133
}
126134
}
127135

0 commit comments

Comments
 (0)