ory/hydra · warning
Token is expired
Error message
Token is expired
What it means
jwt.MapClaims.Valid() (forked from golang-jwt) checks registered claims. When VerifyExpiresAt fails — the `exp` claim is in the past relative to TimeFunc() — it records 'Token is expired' into the ValidationError along with ValidationErrorExpired, aggregated with any other claim failures before returning.
Source
Thrown at fosite/token/jwt/map_claims.go:115
if err != nil {
return 0, false
}
return int64(vf), true
}
return 0, false
}
// Validates time based claims "exp, iat, nbf".
// There is no accounting for clock skew.
// As well, if any of the above claims are not in the token, it will still
// be considered a valid claim.
func (m MapClaims) Valid() error {
vErr := new(ValidationError)
now := TimeFunc().Unix()
if !m.VerifyExpiresAt(now, false) {
vErr.Inner = errors.New("Token is expired")
vErr.Errors |= ValidationErrorExpired
}
if !m.VerifyIssuedAt(now, false) {
vErr.Inner = errors.New("Token used before issued")
vErr.Errors |= ValidationErrorIssuedAt
}
if !m.VerifyNotBefore(now, false) {
vErr.Inner = errors.New("Token is not valid yet")
vErr.Errors |= ValidationErrorNotValidYet
}
if vErr.valid() {
return nil
}
return vErrView on GitHub (pinned to 4174065ffb)
Solutions
- Refresh the token / obtain a new JWT before the exp deadline
- Fix clock synchronization (NTP) between issuing and validating services
- Issue tokens with a longer exp via lifespan configuration if legitimate lifetimes are too short
- Handle the expired-token error in the caller by triggering the refresh flow rather than failing the request
Example fix
// before
token, err := jwt.ParseWithClaims(raw, claims, keyFunc) // expired token fails
// after
token, err := jwt.ParseWithClaims(raw, claims, keyFunc)
if err != nil && errors.Is(err, jwt.ErrTokenExpired) {
return refresh(ctx) // obtain a fresh token and retry
} Defensive patterns
Strategy: try-catch
Validate before calling
// pre-check expiry before parsing deeply
claims := jwt.MapClaims{}
if exp, ok := claims["exp"].(float64); ok && int64(exp) < time.Now().Unix() {
return errors.New("token already expired; refresh first")
} Try / catch
_, err := jwt.ParseWithClaims(raw, &claims, keyFunc)
if err != nil {
var vErr *jwt.ValidationError
if errors.As(err, &vErr) && vErr.Errors&jwt.ValidationErrorExpired != 0 {
return refreshAccessToken(ctx) // recover via refresh flow
}
return err
} Prevention
- Implement proactive token refresh before exp (e.g. 80% of lifetime)
- Synchronize clocks with NTP across services
- Never cache tokens beyond their exp; check expiry before reuse
When it happens
Trigger: Parsing/validating a JWT whose exp claim is earlier than the current time — ParseWithClaims → MapClaims.Valid(); also triggered when the token carries no exp while validation requires it (VerifyExpiresAt returns false).
Common situations: Long-lived access tokens past their lifespan; clocks skewed between token issuer and validator; cached tokens reused after expiry; refresh flow not invoked before the access token lapsed.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- header, body and signature must all be set
- Token used before issued
- Token is not valid yet
- Session must be of type JWTSessionContainer but got type: %T
- GetTokenClaims() must not be nil
AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03).
Data as JSON: /api/errors/c55643416463f88e.
Report an issue: GitHub.