@@ -7,11 +7,11 @@ import { privateKeyPEM, kid as correctKid } from "./authCredentials";
7
7
async function createSignedJWT (
8
8
payload : any ,
9
9
options : {
10
- issuer ?: string ;
11
- audience ?: string ;
10
+ issuer ?: string | null ;
11
+ audience ?: string | null ;
12
12
expiresIn ?: string ;
13
13
subject ?: string ;
14
- issuedAt ?: string ;
14
+ issuedAt ?: string | null ;
15
15
alg ?: "RS256" | "ES256" | ( string & { ignore_me ?: never } ) ;
16
16
useKid ?: "wrong kid" | "missing kid" | "correct kid" ;
17
17
} = { } ,
@@ -34,18 +34,20 @@ async function createSignedJWT(
34
34
? "key-2 (oops, this is the wrong kid!)"
35
35
: undefined ;
36
36
37
- let jwtBuilder = new SignJWT ( payload )
38
- . setProtectedHeader ( {
39
- kid,
40
- alg,
41
- } )
42
- . setIssuedAt ( issuedAt ) ;
37
+ let jwtBuilder = new SignJWT ( payload ) . setProtectedHeader ( {
38
+ kid,
39
+ alg,
40
+ } ) ;
41
+
42
+ if ( issuedAt !== null ) {
43
+ jwtBuilder = jwtBuilder . setIssuedAt ( issuedAt ) ;
44
+ }
43
45
44
- if ( issuer !== undefined ) {
46
+ if ( issuer !== null ) {
45
47
jwtBuilder = jwtBuilder . setIssuer ( issuer ) ;
46
48
}
47
49
48
- if ( audience !== undefined ) {
50
+ if ( audience !== null ) {
49
51
jwtBuilder = jwtBuilder . setAudience ( audience ) ;
50
52
}
51
53
@@ -119,16 +121,6 @@ describe("auth debugging", () => {
119
121
expect ( logger . logs ) . toEqual ( [ ] ) ;
120
122
} ) ;
121
123
122
- test ( "missing kid" , async ( ) => {
123
- const error = await getErrorFromJwt (
124
- await createSignedJWT ( { name : "Presley" } , { useKid : "missing kid" } ) ,
125
- ) ;
126
- // The exact error message may vary, but should be enhanced
127
- expect ( error . code ) . toEqual ( "InvalidAuthHeader" ) ;
128
- expect ( error . message ) . toContain ( "JWT" ) ;
129
- expect ( logger . logs ) . toEqual ( [ ] ) ;
130
- } ) ;
131
-
132
124
test ( "no auth provider found - enhanced error message" , async ( ) => {
133
125
const error = await getErrorFromJwt (
134
126
await createSignedJWT (
@@ -149,7 +141,7 @@ describe("auth debugging", () => {
149
141
"CustomJWT(issuer=https://issuer.example.com/1, app_id=App 1)" ,
150
142
) ;
151
143
expect ( error . message ) . toContain (
152
- "CustomJWT(issuer=https://issuer.example.com/2 , app_id=none)" ,
144
+ "CustomJWT(issuer=https://issuer.example.com/no-aud-specified , app_id=none)" ,
153
145
) ;
154
146
expect ( error . message ) . toContain (
155
147
"CustomJWT(issuer=https://issuer.example.com/3, app_id=App 3)" ,
@@ -158,35 +150,73 @@ describe("auth debugging", () => {
158
150
} ) ;
159
151
160
152
test ( "missing issuer claim" , async ( ) => {
161
- // Create a JWT without an issuer claim
162
- try {
163
- const jwt = await createSignedJWT ( { name : "Presley" } , {
164
- issuer : undefined ,
165
- } as any ) ;
166
- const error = await getErrorFromJwt ( jwt ) ;
167
- expect ( error . code ) . toEqual ( "InvalidAuthHeader" ) ;
168
- expect ( error . message ) . toContain ( "issuer" ) ;
169
- expect ( error . message ) . toContain ( "iss" ) ;
170
- } catch ( e ) {
171
- // JWT creation might fail, which is also valid behavior
172
- console . log ( "JWT creation failed for missing issuer" ) ;
173
- }
153
+ const jwt = await createSignedJWT (
154
+ { name : "Presley" } ,
155
+ {
156
+ issuer : null ,
157
+ } ,
158
+ ) ;
159
+ const error = await getErrorFromJwt ( jwt ) ;
160
+ expect ( error . code ) . toEqual ( "InvalidAuthHeader" ) ;
161
+ expect ( error . message ) . toContain ( "issuer" ) ;
162
+ expect ( error . message ) . toContain ( "iss" ) ;
174
163
} ) ;
175
164
176
165
test ( "missing audience claim" , async ( ) => {
177
- // Create a JWT without an audience claim
178
- try {
179
- const jwt = await createSignedJWT ( { name : "Presley" } , {
180
- audience : undefined ,
181
- } as any ) ;
182
- const error = await getErrorFromJwt ( jwt ) ;
183
- expect ( error . code ) . toEqual ( "InvalidAuthHeader" ) ;
184
- expect ( error . message ) . toContain ( "audience" ) ;
185
- expect ( error . message ) . toContain ( "aud" ) ;
186
- } catch ( e ) {
187
- // JWT creation might fail, which is also valid behavior
188
- console . log ( "JWT creation failed for missing audience" ) ;
189
- }
166
+ const jwt = await createSignedJWT (
167
+ { name : "Presley" } ,
168
+ {
169
+ audience : null ,
170
+ } ,
171
+ ) ;
172
+ const error = await getErrorFromJwt ( jwt ) ;
173
+ expect ( error . code ) . toEqual ( "NoAuthProvider" ) ;
174
+ } ) ;
175
+
176
+ test ( "missing kid" , async ( ) => {
177
+ const error = await getErrorFromJwt (
178
+ await createSignedJWT ( { name : "Presley" } , { useKid : "missing kid" } ) ,
179
+ ) ;
180
+ expect ( error . code ) . toEqual ( "InvalidAuthHeader" ) ;
181
+ expect ( error . message ) . toContain ( "missing a 'kid'" ) ;
182
+ expect ( logger . logs ) . toEqual ( [ ] ) ;
183
+ } ) ;
184
+
185
+ test ( "wrong audience claim" , async ( ) => {
186
+ const jwt = await createSignedJWT (
187
+ { name : "Presley" } ,
188
+ {
189
+ audience : "asdf" ,
190
+ } ,
191
+ ) ;
192
+ const error = await getErrorFromJwt ( jwt ) ;
193
+ expect ( error . code ) . toEqual ( "NoAuthProvider" ) ;
194
+ } ) ;
195
+
196
+ test ( "audience claim allowed when none required" , async ( ) => {
197
+ const jwt = await createSignedJWT (
198
+ { name : "Presley" } ,
199
+ {
200
+ issuer : "https://issuer.example.com/no-aud-specified" ,
201
+ audience : "asdf" ,
202
+ } ,
203
+ ) ;
204
+ httpClient . setAuth ( jwt ) ;
205
+ const result = await httpClient . query ( api . auth . q ) ;
206
+ expect ( result ?. name ) . toEqual ( "Presley" ) ;
207
+ } ) ;
208
+
209
+ test ( "missing audience claim allowed when none required" , async ( ) => {
210
+ const jwt = await createSignedJWT (
211
+ { name : "Presley" } ,
212
+ {
213
+ issuer : "https://issuer.example.com/no-aud-specified" ,
214
+ audience : null ,
215
+ } ,
216
+ ) ;
217
+ httpClient . setAuth ( jwt ) ;
218
+ const result = await httpClient . query ( api . auth . q ) ;
219
+ expect ( result ?. name ) . toEqual ( "Presley" ) ;
190
220
} ) ;
191
221
192
222
test ( "wrong kid" , async ( ) => {
@@ -209,6 +239,7 @@ describe("auth debugging", () => {
209
239
expect ( error . message ) . toContain ( "three base64-encoded parts" ) ;
210
240
} ) ;
211
241
242
+ // Integration tests that hit real APIs are a bummer, TODO hit something else
212
243
// eslint-disable-next-line jest/no-disabled-tests
213
244
test . skip ( "unreachable JWKS URL" , async ( ) => {
214
245
// Use App 3 which has a non-existent JWKS URL
@@ -244,6 +275,18 @@ describe("auth debugging", () => {
244
275
expect ( error . message ) . toContain ( "not valid JSON" ) ;
245
276
} ) ;
246
277
278
+ test ( "token expired 10 seconds ago" , async ( ) => {
279
+ const error = await getErrorFromJwt (
280
+ await createSignedJWT (
281
+ { name : "Presley" } ,
282
+ { issuedAt : "20 sec ago" , expiresIn : "10 sec ago" } ,
283
+ ) ,
284
+ ) ;
285
+ expect ( error . code ) . toEqual ( "InvalidAuthHeader" ) ;
286
+ expect ( error . message ) . toContain ( "Token expired" ) ;
287
+ expect ( error . message ) . toContain ( "seconds ago" ) ;
288
+ } ) ;
289
+
247
290
test ( "token issued 3 seconds in future" , async ( ) => {
248
291
// Should succeed with 5-second tolerance
249
292
httpClient . setAuth (
@@ -265,8 +308,17 @@ describe("auth debugging", () => {
265
308
) ,
266
309
) ;
267
310
expect ( error . code ) . toEqual ( "InvalidAuthHeader" ) ;
268
- expect ( error . message ) . toContain ( "timing issues" ) ;
269
- expect ( error . message ) . toContain ( "iat" ) ;
311
+ expect ( error . message ) . toContain ( "will be valid in" ) ;
312
+ } ) ;
313
+
314
+ // Not recommended (some client logic may expect an iat) but not required.
315
+ test ( "missing iat" , async ( ) => {
316
+ httpClient . setAuth (
317
+ await createSignedJWT ( { name : "Presley" } , { issuedAt : null } ) ,
318
+ ) ;
319
+ const result = await httpClient . query ( api . auth . q ) ;
320
+ expect ( result ?. subject ) . toEqual ( "The Subject" ) ;
321
+ expect ( result ?. name ) . toEqual ( "Presley" ) ;
270
322
} ) ;
271
323
} ) ;
272
324
} ) ;
0 commit comments