ory/hydra · error
Token used before issued
Error message
Token used before issued
What it means
This error comes from JWT claims validation in fosite's jwt package. During `Valid()`, `VerifyIssuedAt(now, false)` fails when the token's `iat` claim is in the future relative to the verifier's clock, i.e. the token is being used before its issue time. The library rejects such tokens because a future `iat` usually indicates clock skew or a forged/misconfigured token.
Source
Thrown at fosite/token/jwt/map_claims.go:120
}
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 vErr
}
func (m MapClaims) UnmarshalJSON(b []byte) error {
// This custom unmarshal allows to configure the
// go-jose decoding settings since there is no other wayView on GitHub (pinned to 4174065ffb)
Solutions
- Synchronize clocks on the token issuer and verifier (enable NTP/chrony and verify with `timedatectl status`).
- Re-mint the token so its `iat` is <= current time.
- If small skew is acceptable, parse with a leeway-enabled validator that subtracts skew from `now` before VerifyIssuedAt.
- Check for wrongly configured time in containers (host clock passthrough) and restarted VMs.
Example fix
// before: strict parsing fails on minor skew
claims, err := jwt.ParseWithClaims(raw, &claims, keyFunc)
// after: allow e.g. 2 minutes of leeway by skewing the reference time
now := time.Now().Add(-2 * time.Minute)
claims, err := jwt.ParseWithClaims(raw, &claims, keyFunc, jwt.WithTimeFunc(func() time.Time { return now })) Defensive patterns
Strategy: validation
Validate before calling
// before parsing, sanity-check token timestamps against local clock
func iatLooksSane(claims jwt.MapClaims, leeway time.Duration) bool {
iat, ok := claims["iat"].(float64)
return ok && time.Unix(int64(iat), 0).Before(time.Now().Add(leeway))
} Try / catch
// treat expired/issued-at failures as auth failures, not crashes
if err := claims.Valid(); err != nil {
var vErr *jwt.ValidationError
if errors.As(err, &vErr) && vErr.Errors&jwt.ValidationErrorIssuedAt != 0 {
return nil, fmt.Errorf("token not yet valid (clock skew?): %w", err)
}
return nil, err
} Prevention
- Run NTP/chrony on all hosts and containers issuing or verifying tokens
- Allow explicit leeway (1-2 min) in token validation
- Monitor clock drift across the fleet
- Never hand-mint tokens with guessed iat values
When it happens
Trigger: Calling ParseWithClaims on a JWT whose `iat` claim is greater than the current time — typically when the issuer's clock is ahead of the verifier's clock, or the token was crafted with a wrong `iat`.
Common situations: Distributed deployments where the OAuth server and resource server clocks drift apart; tokens issued by a service with an unsynchronized NTP daemon; containers/VMs resumed from snapshots with stale clocks; manually minted test tokens with a mistaken epoch timestamp.
Related errors
- Token is not valid yet
- Token is expired
- Session must be of type JWTSessionContainer but got type: %T
- GetTokenClaims() must not be nil
- device_challenge is required
AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03).
Data as JSON: /api/errors/c882a81b84591d7f.
Report an issue: GitHub.