{"record":{"id":"f0aa5e5593489a2d","repo":"vxcontrol/pentagi","slug":"token-is-either-expired-or-not-active-yet","errorCode":null,"errorMessage":"token is either expired or not active yet","messagePattern":"token is either expired or not active yet","errorType":"validation","errorClass":null,"httpStatus":401,"severity":"error","filePath":"backend/pkg/server/auth/api_token_jwt.go","lineNumber":51,"sourceCode":"\t\t\tSubject:   \"api_token\",\n\t\t},\n\t}\n}\n\nfunc ValidateAPIToken(tokenString, globalSalt string) (*models.APITokenClaims, error) {\n\tvar claims models.APITokenClaims\n\ttoken, err := jwt.ParseWithClaims(tokenString, &claims, func(token *jwt.Token) (any, error) {\n\t\t// verify signing algorithm to prevent \"alg: none\"\n\t\tif _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {\n\t\t\treturn nil, fmt.Errorf(\"unexpected signing method: %v\", token.Header[\"alg\"])\n\t\t}\n\t\treturn MakeJWTSigningKey(globalSalt), nil\n\t})\n\tif err != nil {\n\t\tif errors.Is(err, jwt.ErrTokenMalformed) {\n\t\t\treturn nil, fmt.Errorf(\"token is malformed\")\n\t\t} else if errors.Is(err, jwt.ErrTokenExpired) || errors.Is(err, jwt.ErrTokenNotValidYet) {\n\t\t\treturn nil, fmt.Errorf(\"token is either expired or not active yet\")\n\t\t} else {\n\t\t\treturn nil, fmt.Errorf(\"token invalid: %w\", err)\n\t\t}\n\t}\n\n\tif !token.Valid {\n\t\treturn nil, fmt.Errorf(\"token is invalid\")\n\t}\n\n\treturn &claims, nil\n}\n","sourceCodeStart":33,"sourceCodeEnd":63,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/server/auth/api_token_jwt.go#L33-L63","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nttl := uint64(60) // 1 minute, expires almost immediately\ntoken, _ := auth.MakeAPIToken(globalSalt, auth.MakeAPITokenClaims(tokenID, uhash, uid, rid, ttl))\n// after\nttl := uint64(3600) // 1 hour\ntoken, _ := auth.MakeAPIToken(globalSalt, auth.MakeAPITokenClaims(tokenID, uhash, uid, rid, ttl))","handlingStrategy":"retry","validationCode":"// client-side pre-check before using a cached token\nclaims, _ := jwt.ParseUnverified(token)\nif exp := claims.Claims.(jwt.MapClaims)[\"exp\"]; exp == nil || float64(time.Now().Unix()) >= exp.(float64) {\n    token = issueNewToken() // refresh before calling\n}","typeGuard":"func tokenLooksExpired(tok string) bool {\n    p, _ := jwt.NewParser().ParseUnverified(tok, &jwt.MapClaims{})\n    if p == nil { return true }\n    exp, _ := p.Claims.(*jwt.MapClaims).GetExpirationTime()\n    return exp == nil || time.Now().Unix() >= int64(*exp)\n}","tryCatchPattern":"if strings.Contains(err.Error(), \"expired or not active yet\") {\n    token = issueNewToken()\n    return doAuthenticatedRequest(token) // single retry with fresh token\n}\nreturn err","preventionTips":["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"],"tags":["jwt","authentication","token-expired"],"backgroundTag":"jwt-token-expired","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}