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 way

View on GitHub (pinned to 4174065ffb)

Solutions

  1. Synchronize clocks on the token issuer and verifier (enable NTP/chrony and verify with `timedatectl status`).
  2. Re-mint the token so its `iat` is <= current time.
  3. If small skew is acceptable, parse with a leeway-enabled validator that subtracts skew from `now` before VerifyIssuedAt.
  4. 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

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


AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03). Data as JSON: /api/errors/c882a81b84591d7f. Report an issue: GitHub.