@@ -41,7 +41,7 @@ type Config struct {
4141
4242// TokenValidator interface for OAuth token validation
4343type TokenValidator interface {
44- ValidateToken (ctx context.Context , token string ) (* User , error )
44+ ValidateToken (ctx context.Context , token string ) (* User , time. Time , error )
4545 Initialize (cfg * Config ) error
4646}
4747
@@ -80,7 +80,7 @@ func (v *HMACValidator) Initialize(cfg *Config) error {
8080}
8181
8282// ValidateToken validates JWT token using HMAC-SHA256
83- func (v * HMACValidator ) ValidateToken (ctx context.Context , tokenString string ) (* User , error ) {
83+ func (v * HMACValidator ) ValidateToken (ctx context.Context , tokenString string ) (* User , time. Time , error ) {
8484 // Note: ctx parameter accepted for interface compliance, but HMAC validation is local-only (no I/O)
8585 // Remove Bearer prefix if present
8686 tokenString = strings .TrimPrefix (tokenString , "Bearer " )
@@ -94,26 +94,26 @@ func (v *HMACValidator) ValidateToken(ctx context.Context, tokenString string) (
9494 return []byte (v .secret ), nil
9595 })
9696 if err != nil {
97- return nil , fmt .Errorf ("failed to parse and validate token: %w" , err )
97+ return nil , time. Time {}, fmt .Errorf ("failed to parse and validate token: %w" , err )
9898 }
9999
100100 if ! token .Valid {
101- return nil , fmt .Errorf ("invalid token" )
101+ return nil , time. Time {}, fmt .Errorf ("invalid token" )
102102 }
103103
104104 claims , ok := token .Claims .(jwt.MapClaims )
105105 if ! ok {
106- return nil , fmt .Errorf ("invalid token claims" )
106+ return nil , time. Time {}, fmt .Errorf ("invalid token claims" )
107107 }
108108
109109 // Validate required claims including audience
110110 if err := validateTokenClaims (claims ); err != nil {
111- return nil , fmt .Errorf ("token validation failed: %w" , err )
111+ return nil , time. Time {}, fmt .Errorf ("token validation failed: %w" , err )
112112 }
113113
114114 // Validate audience claim for security
115115 if err := v .validateAudience (claims ); err != nil {
116- return nil , fmt .Errorf ("audience validation failed: %w" , err )
116+ return nil , time. Time {}, fmt .Errorf ("audience validation failed: %w" , err )
117117 }
118118
119119 // Extract user information
@@ -124,10 +124,17 @@ func (v *HMACValidator) ValidateToken(ctx context.Context, tokenString string) (
124124 }
125125
126126 if user .Subject == "" {
127- return nil , fmt .Errorf ("missing subject in token" )
127+ return nil , time. Time {}, fmt .Errorf ("missing subject in token" )
128128 }
129129
130- return user , nil
130+ // Extract expiry from already-parsed claims defaulting to 5 minutes in the
131+ // future.
132+ expiry := time .Now ().Add (5 * time .Minute )
133+ if expVal , ok := claims ["exp" ].(float64 ); ok {
134+ expiry = time .Unix (int64 (expVal ), 0 )
135+ }
136+
137+ return user , expiry , nil
131138}
132139
133140// validateAudience validates the audience claim matches the expected value
@@ -223,7 +230,7 @@ func (v *OIDCValidator) Initialize(cfg *Config) error {
223230}
224231
225232// ValidateToken validates JWT token using OIDC/JWKS
226- func (v * OIDCValidator ) ValidateToken (ctx context.Context , tokenString string ) (* User , error ) {
233+ func (v * OIDCValidator ) ValidateToken (ctx context.Context , tokenString string ) (* User , time. Time , error ) {
227234 // Remove Bearer prefix if present
228235 tokenString = strings .TrimPrefix (tokenString , "Bearer " )
229236
@@ -234,7 +241,7 @@ func (v *OIDCValidator) ValidateToken(ctx context.Context, tokenString string) (
234241 // go-oidc handles RSA signature validation, JWKS fetching, and key rotation
235242 idToken , err := v .verifier .Verify (ctx , tokenString )
236243 if err != nil {
237- return nil , fmt .Errorf ("token verification failed: %w" , err )
244+ return nil , time. Time {}, fmt .Errorf ("token verification failed: %w" , err )
238245 }
239246
240247 // Extract claims from verified token
@@ -253,28 +260,28 @@ func (v *OIDCValidator) ValidateToken(ctx context.Context, tokenString string) (
253260 }
254261
255262 if err := idToken .Claims (& claims ); err != nil {
256- return nil , fmt .Errorf ("failed to extract claims: %w" , err )
263+ return nil , time. Time {}, fmt .Errorf ("failed to extract claims: %w" , err )
257264 }
258265
259266 // Extract raw claims for audience validation
260267 var rawClaims jwt.MapClaims
261268 if err := idToken .Claims (& rawClaims ); err != nil {
262- return nil , fmt .Errorf ("failed to extract raw claims: %w" , err )
269+ return nil , time. Time {}, fmt .Errorf ("failed to extract raw claims: %w" , err )
263270 }
264271
265272 // Run extra validation functions
266273 for i , fn := range v .TokenValidators {
267274 err := fn (rawClaims )
268275 if err != nil {
269- return nil , fmt .Errorf ("validation function %d failed with error: %w" , i , err )
276+ return nil , time. Time {}, fmt .Errorf ("validation function %d failed with error: %w" , i , err )
270277 }
271278 }
272279
273280 return & User {
274281 Subject : claims .Subject ,
275282 Username : claims .PreferredUsername ,
276283 Email : claims .Email ,
277- }, nil
284+ }, idToken . Expiry , nil
278285}
279286
280287// validateAudience validates the audience claim matches the expected value for OIDC tokens
0 commit comments