vxcontrol/pentagi · error
token is either expired or not active yet
Error message
token is either expired or not active yet
What it means
ValidateAPIToken classifies jwt.ErrTokenExpired or jwt.ErrTokenNotValidYet from jwt/v5 into "token is either expired or not active yet". The token parsed correctly but its exp claim is in the past or its nbf (not-before) claim is in the future relative to server time.
Source
Thrown at backend/pkg/server/auth/api_token_jwt.go:51
Subject: "api_token",
},
}
}
func ValidateAPIToken(tokenString, globalSalt string) (*models.APITokenClaims, error) {
var claims models.APITokenClaims
token, err := jwt.ParseWithClaims(tokenString, &claims, func(token *jwt.Token) (any, error) {
// verify signing algorithm to prevent "alg: none"
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return MakeJWTSigningKey(globalSalt), nil
})
if err != nil {
if errors.Is(err, jwt.ErrTokenMalformed) {
return nil, fmt.Errorf("token is malformed")
} else if errors.Is(err, jwt.ErrTokenExpired) || errors.Is(err, jwt.ErrTokenNotValidYet) {
return nil, fmt.Errorf("token is either expired or not active yet")
} else {
return nil, fmt.Errorf("token invalid: %w", err)
}
}
if !token.Valid {
return nil, fmt.Errorf("token is invalid")
}
return &claims, nil
}
View on GitHub (pinned to ea665308ba)
Solutions
- Have the client request/generate a new API token and retry; tokens are short-lived by design
- Verify server clock sync (NTP/chrony) on both issuer and validator to rule out skew
- Increase the ttl passed to MakeAPITokenClaims if tokens legitimately need a longer lifetime
- Confirm jwt.WithTimeFunc/leeway defaults are acceptable; add leeway if minor drift is expected
Example fix
// before ttl := uint64(60) // 1 minute, expires almost immediately token, _ := auth.MakeAPIToken(globalSalt, auth.MakeAPITokenClaims(tokenID, uhash, uid, rid, ttl)) // after ttl := uint64(3600) // 1 hour token, _ := auth.MakeAPIToken(globalSalt, auth.MakeAPITokenClaims(tokenID, uhash, uid, rid, ttl))
Defensive patterns
Strategy: retry
Validate before calling
// client-side pre-check before using a cached token
claims, _ := jwt.ParseUnverified(token)
if exp := claims.Claims.(jwt.MapClaims)["exp"]; exp == nil || float64(time.Now().Unix()) >= exp.(float64) {
token = issueNewToken() // refresh before calling
} Type guard
func tokenLooksExpired(tok string) bool {
p, _ := jwt.NewParser().ParseUnverified(tok, &jwt.MapClaims{})
if p == nil { return true }
exp, _ := p.Claims.(*jwt.MapClaims).GetExpirationTime()
return exp == nil || time.Now().Unix() >= int64(*exp)
} Try / catch
if strings.Contains(err.Error(), "expired or not active yet") {
token = issueNewToken()
return doAuthenticatedRequest(token) // single retry with fresh token
}
return err Prevention
- Refresh the token client-side before expiry instead of caching until failure
- Keep server clocks NTP-synced on both issuer and validator
- Choose a realistic ttl in MakeAPITokenClaims for the integration's lifetime needs
- Retry once with a fresh token before surfacing auth failures
When it happens
Trigger: Validating an API token whose ttl (used in MakeAPITokenClaims ExpiresAt = now + ttl seconds) has elapsed, or a token with a future IssuedAt/nbf, or validation happening on a server whose clock differs significantly from the issuing host.
Common situations: Long-lived API token finally expired after ttl seconds; client cached an old token past its expiry; server clocks skewed (VM drift, wrong TZ/RTC) making a fresh token appear not-yet-valid; tests using static timestamps.
Related errors
AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01).
Data as JSON: /api/errors/f0aa5e5593489a2d.
Report an issue: GitHub.