{"record":{"id":"7faf2752b837db56","repo":"crowdsecurity/crowdsec","slug":"w-exp","errorCode":null,"errorMessage":"%w: exp","messagePattern":"%w: exp","errorType":"exception","errorClass":"ErrTokenMissingClaim","httpStatus":null,"severity":"error","filePath":"pkg/database/token.go","lineNumber":53,"sourceCode":"\tif token == \"\" {\n\t\treturn APICToken{}, ErrTokenNotFound\n\t}\n\n\tparser := new(jwt.Parser)\n\n\ttok, _, err := parser.ParseUnverified(token, jwt.MapClaims{})\n\tif err != nil {\n\t\treturn APICToken{}, fmt.Errorf(\"%w: %s\", ErrTokenParse, err)\n\t}\n\n\tclaims, ok := tok.Claims.(jwt.MapClaims)\n\tif !ok {\n\t\treturn APICToken{}, ErrTokenParse\n\t}\n\n\texpFloat, ok := claims[\"exp\"].(float64)\n\tif !ok {\n\t\treturn APICToken{}, fmt.Errorf(\"%w: exp\", ErrTokenMissingClaim)\n\t}\n\n\texp := time.Unix(int64(expFloat), 0)\n\tif time.Now().UTC().After(exp.Add(-1 * time.Minute)) {\n\t\treturn APICToken{}, ErrTokenExpired\n\t}\n\n\treturn APICToken{Raw: token, ExpiresAt: exp}, nil\n}\n\n// SaveAPICToken stores the given JWT token in the local database under the appropriate config item.\nfunc (c *Client) SaveAPICToken(ctx context.Context, token string) error {\n\tif err := c.SetConfigItem(ctx, APICTokenKey, token); err != nil {\n\t\treturn fmt.Errorf(\"saving token: %w\", err)\n\t}\n\n\treturn nil\n}","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/crowdsecurity/crowdsec/blob/909b5157986a2b2c2163300fdaef5ed01289f7d2/pkg/database/token.go#L35-L71","documentation":"LoadAPICToken requires the JWT 'exp' claim. If the decoded claims lack a numeric exp (or it's not a float64 as produced by the JWT library), the error wraps ErrTokenMissingClaim with ': exp'. A token without a usable expiration is treated as unverifiable and rejected even if otherwise valid.","triggerScenarios":"Calling LoadAPICToken with a stored JWT whose payload omits 'exp' or has it as a string/non-numeric value — e.g. a token issued by a different/newer service version, or a hand-crafted token pasted into the DB.","commonSituations":"Tokens minted by a custom or misconfigured CAPI/auth service without exp; manual token injection during testing; upgrading from a version whose tokens had a different claim set.","solutions":["Re-authenticate to obtain a new token that includes exp: 'cscli capi delete' + 'cscli capi register', then login.","Ensure any token provisioning tooling issues standard JWTs with a numeric Unix-seconds exp claim.","Don't paste manually generated tokens into the DB; use cscli."],"exampleFix":"// caller: missing/expired claims both mean 'get a fresh token'\ntok, err := c.LoadAPICToken(ctx, log)\nif errors.Is(err, database.ErrTokenMissingClaim) || errors.Is(err, database.ErrTokenExpired) {\n    return authenticate(ctx) // issues a token with exp\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"func isMissingExpErr(err error) bool { return errors.Is(err, database.ErrTokenMissingClaim) }","tryCatchPattern":"tok, err := client.LoadAPICToken(ctx, log)\nif errors.Is(err, database.ErrTokenMissingClaim) {\n    log.Warn(\"token has no exp claim, obtaining a new one\")\n    return reauthenticate(ctx)\n}","preventionTips":["Only store tokens issued by the standard CAPI auth flow (they include exp).","If minting tokens with custom tooling, always set a numeric Unix-seconds exp claim.","Re-authenticate instead of reusing foreign-version tokens.","Test token provisioning end-to-end after version upgrades."],"tags":["jwt","token","missing-claim","capi"],"backgroundTag":"jwt-token-expired","analyzedSha":"909b5157986a2b2c2163300fdaef5ed01289f7d2","analyzedAt":"2026-09-06T12:27:26.012Z","contentChangedAt":"2026-09-06T12:27:26.012Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}