go-kit/kit · error · ErrTokenExpired
JWT is expired
Error message
JWT is expired
What it means
Returned by jwt.NewParser when the underlying jwt.ValidationError has the ValidationErrorExpired bit set: the token's exp claim is in the past. The token may be otherwise perfectly well-formed and correctly signed — it has simply aged out of its validity window.
Source
Thrown at auth/jwt/middleware.go:36
//
// Deprecated: prefer JWTContextKey.
JWTTokenContextKey = JWTContextKey
// JWTClaimsContextKey holds the key used to store the JWT Claims in the
// context.
JWTClaimsContextKey contextKey = "JWTClaims"
)
var (
// ErrTokenContextMissing denotes a token was not passed into the parsing
// middleware's context.
ErrTokenContextMissing = errors.New("token up for parsing was not passed through the context")
// ErrTokenInvalid denotes a token was not able to be validated.
ErrTokenInvalid = errors.New("JWT was invalid")
// ErrTokenExpired denotes a token's expire header (exp) has since passed.
ErrTokenExpired = errors.New("JWT is expired")
// ErrTokenMalformed denotes a token was not formatted as a JWT.
ErrTokenMalformed = errors.New("JWT is malformed")
// ErrTokenNotActive denotes a token's not before header (nbf) is in the
// future.
ErrTokenNotActive = errors.New("token is not valid yet")
// ErrUnexpectedSigningMethod denotes a token was signed with an unexpected
// signing method.
ErrUnexpectedSigningMethod = errors.New("unexpected signing method")
)
// NewSigner creates a new JWT generating middleware, specifying key ID,
// signing string, signing method and the claims you would like it to contain.
// Tokens are signed with a Key ID header (kid) which is useful for determining
// the key to use for parsing. Particularly useful for clients.
func NewSigner(kid string, key []byte, method jwt.SigningMethod, claims jwt.Claims) endpoint.Middleware {View on GitHub (pinned to 78fbbceece)
Solutions
- Implement refresh: on this error, obtain a new token (re-login or refresh grant) and retry the request once
- Issue tokens with an appropriate exp (e.g. 15–60 min) and refresh them proactively before expiry
- Sync clocks (NTP/chrony) on signer and verifier to eliminate skew
- Ensure exp is expressed in seconds since epoch (NumericDate), not milliseconds
Example fix
// before: token signed once with a short life and reused forever
claims := &jwt.RegisteredClaims{ExpiresAt: jwt.NewNumericDate(time.Now().Add(5 * time.Minute))}
// after: sane lifetime plus client-side refresh-and-retry
if _, err := ep(ctx, req); err != nil && errors.Is(err, jwt.ErrTokenExpired) {
newTok := refreshToken() // re-auth / refresh grant
ctx = context.WithValue(ctx, jwt.JWTContextKey, newTok)
_, err = ep(ctx, req)
} Defensive patterns
Strategy: retry
Validate before calling
null
Type guard
null
Try / catch
resp, err := ep(ctx, req)
if err != nil && errors.Is(err, jwt.ErrTokenExpired) {
if tok, ok := refreshToken(); ok { // re-login or refresh grant
ctx = context.WithValue(ctx, jwt.JWTContextKey, tok)
resp, err = ep(ctx, req) // single retry with the fresh token
}
}
return resp, err Prevention
- Refresh tokens proactively at ~80% of lifetime instead of waiting for rejection
- Run NTP on all hosts so expiry decisions are trustworthy
- Store tokens with their expiry and have background jobs fetch new ones before starting work
When it happens
Trigger: Calling a protected endpoint with a token whose exp has elapsed; long-lived client (CLI, mobile app, background job) reusing a token issued hours/days earlier; clock skew between the issuing host and the verifying server making a just-issued token appear expired; signer that computed exp in milliseconds instead of seconds.
Common situations: No token refresh flow implemented; tokens minted with a very short lifetime; NTP drift on VMs/containers; migration from one auth provider that used different token lifetimes; caching a token in config instead of fetching fresh ones.
Related errors
- token up for parsing was not passed through the context
- JWT was invalid
- JWT is malformed
- token is not valid yet
- unexpected signing method
AI-assisted analysis of go-kit/kit@78fbbceece (2026-08-15).
Data as JSON: /api/errors/0dbdcee21e191737.
Report an issue: GitHub.