{"record":{"id":"c55643416463f88e","repo":"ory/hydra","slug":"token-is-expired","errorCode":null,"errorMessage":"Token is expired","messagePattern":"Token is expired","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"fosite/token/jwt/map_claims.go","lineNumber":115,"sourceCode":"\t\tif err != nil {\n\t\t\treturn 0, false\n\t\t}\n\n\t\treturn int64(vf), true\n\t}\n\treturn 0, false\n}\n\n// Validates time based claims \"exp, iat, nbf\".\n// There is no accounting for clock skew.\n// As well, if any of the above claims are not in the token, it will still\n// be considered a valid claim.\nfunc (m MapClaims) Valid() error {\n\tvErr := new(ValidationError)\n\tnow := TimeFunc().Unix()\n\n\tif !m.VerifyExpiresAt(now, false) {\n\t\tvErr.Inner = errors.New(\"Token is expired\")\n\t\tvErr.Errors |= ValidationErrorExpired\n\t}\n\n\tif !m.VerifyIssuedAt(now, false) {\n\t\tvErr.Inner = errors.New(\"Token used before issued\")\n\t\tvErr.Errors |= ValidationErrorIssuedAt\n\t}\n\n\tif !m.VerifyNotBefore(now, false) {\n\t\tvErr.Inner = errors.New(\"Token is not valid yet\")\n\t\tvErr.Errors |= ValidationErrorNotValidYet\n\t}\n\n\tif vErr.valid() {\n\t\treturn nil\n\t}\n\n\treturn vErr","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/ory/hydra/blob/4174065ffb052799890f7480f5360a877a67ffc1/fosite/token/jwt/map_claims.go#L97-L133","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\ntoken, err := jwt.ParseWithClaims(raw, claims, keyFunc) // expired token fails\n// after\ntoken, err := jwt.ParseWithClaims(raw, claims, keyFunc)\nif err != nil && errors.Is(err, jwt.ErrTokenExpired) {\n    return refresh(ctx) // obtain a fresh token and retry\n}","handlingStrategy":"try-catch","validationCode":"// pre-check expiry before parsing deeply\nclaims := jwt.MapClaims{}\nif exp, ok := claims[\"exp\"].(float64); ok && int64(exp) < time.Now().Unix() {\n    return errors.New(\"token already expired; refresh first\")\n}","typeGuard":null,"tryCatchPattern":"_, err := jwt.ParseWithClaims(raw, &claims, keyFunc)\nif err != nil {\n    var vErr *jwt.ValidationError\n    if errors.As(err, &vErr) && vErr.Errors&jwt.ValidationErrorExpired != 0 {\n        return refreshAccessToken(ctx) // recover via refresh flow\n    }\n    return err\n}","preventionTips":["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"],"tags":["jwt","token-expired","fosite","validation"],"backgroundTag":"jwt-token-expired","analyzedSha":"4174065ffb052799890f7480f5360a877a67ffc1","analyzedAt":"2026-09-03T14:52:41.581Z","contentChangedAt":"2026-09-03T14:52:41.581Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}