@@ -160,6 +160,66 @@ func (ts *AuthTestSuite) TestParseJWTClaims() {
160160 }
161161}
162162
163+ func (ts * AuthTestSuite ) TestParseJWTClaimsWithAllowExpired () {
164+ // Test that allow_expired query parameter controls JWT expiration validation
165+ u , err := models .FindUserByEmailAndAudience (ts .API .db , "test@example.com" , ts .Config .JWT .Aud )
166+ require .NoError (ts .T (), err )
167+
168+ s , err := models .NewSession (u .ID , nil )
169+ require .NoError (ts .T (), err )
170+ require .NoError (ts .T (), ts .API .db .Create (s ))
171+
172+ // Create an expired JWT token (exp set to past time)
173+ expiredClaims := & AccessTokenClaims {
174+ RegisteredClaims : jwt.RegisteredClaims {
175+ Subject : u .ID .String (),
176+ ExpiresAt : jwt .NewNumericDate (jwt .Now ().Add (- 1 * 3600 )), // expired 1 hour ago
177+ },
178+ Role : "authenticated" ,
179+ SessionId : s .ID .String (),
180+ }
181+
182+ expiredJwt , err := jwt .NewWithClaims (jwt .SigningMethodHS256 , expiredClaims ).SignedString ([]byte (ts .Config .JWT .Secret ))
183+ require .NoError (ts .T (), err )
184+
185+ ts .Run ("Without allow_expired parameter - should reject expired token" , func () {
186+ req := httptest .NewRequest (http .MethodGet , "http://localhost/user" , nil )
187+ req .Header .Set ("Authorization" , "Bearer " + expiredJwt )
188+
189+ _ , err := ts .API .parseJWTClaims (expiredJwt , req )
190+ require .Error (ts .T (), err )
191+ require .Contains (ts .T (), err .Error (), "token is expired" )
192+ })
193+
194+ ts .Run ("With allow_expired=false - should reject expired token" , func () {
195+ req := httptest .NewRequest (http .MethodGet , "http://localhost/user?allow_expired=false" , nil )
196+ req .Header .Set ("Authorization" , "Bearer " + expiredJwt )
197+
198+ _ , err := ts .API .parseJWTClaims (expiredJwt , req )
199+ require .Error (ts .T (), err )
200+ require .Contains (ts .T (), err .Error (), "token is expired" )
201+ })
202+
203+ ts .Run ("With allow_expired=true - should accept expired token" , func () {
204+ req := httptest .NewRequest (http .MethodGet , "http://localhost/user?allow_expired=true" , nil )
205+ req .Header .Set ("Authorization" , "Bearer " + expiredJwt )
206+
207+ ctx , err := ts .API .parseJWTClaims (expiredJwt , req )
208+ require .NoError (ts .T (), err )
209+
210+ // Verify token is stored in context
211+ token := getToken (ctx )
212+ require .NotNil (ts .T (), token )
213+ require .Equal (ts .T (), expiredJwt , token .Raw )
214+
215+ // Verify claims are correctly parsed
216+ claims := getClaims (ctx )
217+ require .NotNil (ts .T (), claims )
218+ require .Equal (ts .T (), u .ID .String (), claims .Subject )
219+ require .Equal (ts .T (), "authenticated" , claims .Role )
220+ })
221+ }
222+
163223func (ts * AuthTestSuite ) TestMaybeLoadUserOrSession () {
164224 u , err := models .FindUserByEmailAndAudience (ts .API .db , "test@example.com" , ts .Config .JWT .Aud )
165225 require .NoError (ts .T (), err )
0 commit comments