@@ -7,11 +7,11 @@ import { privateKeyPEM, kid as correctKid } from "./authCredentials";
77async function createSignedJWT (
88 payload : any ,
99 options : {
10- issuer ?: string ;
11- audience ?: string ;
10+ issuer ?: string | null ;
11+ audience ?: string | null ;
1212 expiresIn ?: string ;
1313 subject ?: string ;
14- issuedAt ?: string ;
14+ issuedAt ?: string | null ;
1515 alg ?: "RS256" | "ES256" | ( string & { ignore_me ?: never } ) ;
1616 useKid ?: "wrong kid" | "missing kid" | "correct kid" ;
1717 } = { } ,
@@ -34,18 +34,20 @@ async function createSignedJWT(
3434 ? "key-2 (oops, this is the wrong kid!)"
3535 : undefined ;
3636
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+ }
4345
44- if ( issuer !== undefined ) {
46+ if ( issuer !== null ) {
4547 jwtBuilder = jwtBuilder . setIssuer ( issuer ) ;
4648 }
4749
48- if ( audience !== undefined ) {
50+ if ( audience !== null ) {
4951 jwtBuilder = jwtBuilder . setAudience ( audience ) ;
5052 }
5153
@@ -119,16 +121,6 @@ describe("auth debugging", () => {
119121 expect ( logger . logs ) . toEqual ( [ ] ) ;
120122 } ) ;
121123
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-
132124 test ( "no auth provider found - enhanced error message" , async ( ) => {
133125 const error = await getErrorFromJwt (
134126 await createSignedJWT (
@@ -149,7 +141,7 @@ describe("auth debugging", () => {
149141 "CustomJWT(issuer=https://issuer.example.com/1, app_id=App 1)" ,
150142 ) ;
151143 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)" ,
153145 ) ;
154146 expect ( error . message ) . toContain (
155147 "CustomJWT(issuer=https://issuer.example.com/3, app_id=App 3)" ,
@@ -158,35 +150,73 @@ describe("auth debugging", () => {
158150 } ) ;
159151
160152 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" ) ;
174163 } ) ;
175164
176165 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" ) ;
190220 } ) ;
191221
192222 test ( "wrong kid" , async ( ) => {
@@ -209,6 +239,7 @@ describe("auth debugging", () => {
209239 expect ( error . message ) . toContain ( "three base64-encoded parts" ) ;
210240 } ) ;
211241
242+ // Integration tests that hit real APIs are a bummer, TODO hit something else
212243 // eslint-disable-next-line jest/no-disabled-tests
213244 test . skip ( "unreachable JWKS URL" , async ( ) => {
214245 // Use App 3 which has a non-existent JWKS URL
@@ -244,6 +275,18 @@ describe("auth debugging", () => {
244275 expect ( error . message ) . toContain ( "not valid JSON" ) ;
245276 } ) ;
246277
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+
247290 test ( "token issued 3 seconds in future" , async ( ) => {
248291 // Should succeed with 5-second tolerance
249292 httpClient . setAuth (
@@ -265,8 +308,17 @@ describe("auth debugging", () => {
265308 ) ,
266309 ) ;
267310 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" ) ;
270322 } ) ;
271323 } ) ;
272324} ) ;
0 commit comments