go-kit/kit · warning · ErrTokenNotActive

token is not valid yet

Error message

token is not valid yet

What it means

Returned by jwt.NewParser when the underlying jwt.ValidationError has the ValidationErrorNotValidYet bit set: the token's nbf (not before) claim is in the future. The token is well-formed and correctly signed but is not yet within its validity window, so the server refuses it.

Source

Thrown at auth/jwt/middleware.go:43

)

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 {
	return func(next endpoint.Endpoint) endpoint.Endpoint {
		return func(ctx context.Context, request interface{}) (response interface{}, err error) {
			token := jwt.NewWithClaims(method, claims)
			token.Header["kid"] = kid

			// Sign and get the complete encoded token as a string using the secret
			tokenString, err := token.SignedString(key)

View on GitHub (pinned to 78fbbceece)

Solutions

  1. Sync clocks on all signing and verifying hosts (NTP/chrony)
  2. Set nbf at (or slightly before) issuance — e.g. time.Now().Add(-30*time.Second) — instead of in the future
  3. If skew is unavoidable, tolerate it by validating with leeway on the golang-jwt validator instead of relying on the strict default
  4. Retry the request after (nbf - now) seconds once clocks are correct

Example fix

// before: nbf pushed into the future, verifier clock slightly behind
claims := &jwt.RegisteredClaims{NotBefore: jwt.NewNumericDate(time.Now().Add(2 * time.Minute))}

// after: nbf at issuance minus small skew allowance
claims := &jwt.RegisteredClaims{NotBefore: jwt.NewNumericDate(time.Now().Add(-30 * time.Second))}
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.ErrTokenNotActive) {
	// transient skew: wait briefly and retry once; if it persists, alert on clock sync
	time.Sleep(2 * time.Second)
	resp, err = ep(ctx, req)
}

Prevention

When it happens

Trigger: Clock skew: the verifying server's clock lags behind the issuing server, so a token with nbf=now appears 'not valid yet'; signer deliberately sets nbf in the future; tokens minted with nbf computed in the wrong unit (milliseconds) so it lands far ahead; tokens generated on a host with a wrong system clock.

Common situations: Container/VM clock drift in clusters; multi-region deployments where issuer and verifier disagree on time; switching identity providers that populate nbf differently; CI environments with frozen/skewed clocks.

Related errors


AI-assisted analysis of go-kit/kit@78fbbceece (2026-08-15). Data as JSON: /api/errors/b65a90ab269ae355. Report an issue: GitHub.