{"record":{"id":"fac7d044b123f020","repo":"AlistGo/alist","slug":"token-is-expired","errorCode":null,"errorMessage":"token is expired","messagePattern":"token is expired","errorType":"http","errorClass":null,"httpStatus":401,"severity":"error","filePath":"server/common/auth.go","lineNumber":53,"sourceCode":"\t\treturn \"\", err\n\t}\n\tvalidTokenCache.Set(tokenString, true)\n\treturn tokenString, err\n}\n\nfunc ParseToken(tokenString string) (*UserClaims, error) {\n\ttoken, err := jwt.ParseWithClaims(tokenString, &UserClaims{}, func(token *jwt.Token) (interface{}, error) {\n\t\treturn SecretKey, nil\n\t})\n\tif IsTokenInvalidated(tokenString) {\n\t\treturn nil, errors.New(\"token is invalidated\")\n\t}\n\tif err != nil {\n\t\tif ve, ok := err.(*jwt.ValidationError); ok {\n\t\t\tif ve.Errors&jwt.ValidationErrorMalformed != 0 {\n\t\t\t\treturn nil, errors.New(\"that's not even a token\")\n\t\t\t} else if ve.Errors&jwt.ValidationErrorExpired != 0 {\n\t\t\t\treturn nil, errors.New(\"token is expired\")\n\t\t\t} else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 {\n\t\t\t\treturn nil, errors.New(\"token not active yet\")\n\t\t\t} else {\n\t\t\t\treturn nil, errors.New(\"couldn't handle this token\")\n\t\t\t}\n\t\t}\n\t}\n\tif claims, ok := token.Claims.(*UserClaims); ok && token.Valid {\n\t\treturn claims, nil\n\t}\n\treturn nil, errors.New(\"couldn't handle this token\")\n}\n\nfunc InvalidateToken(tokenString string) error {\n\tif tokenString == \"\" {\n\t\treturn nil // don't invalidate empty guest token\n\t}\n\tvalidTokenCache.Del(tokenString)","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/server/common/auth.go#L35-L71","documentation":"ParseToken maps jwt.ValidationErrorExpired to this message: the JWT parsed structurally but its exp claim (set from conf.Conf.TokenExpiresIn hours at issue time) is in the past.","triggerScenarios":"Using a login token past its lifetime (default configured via TokenExpiresIn; 0 disables expiry in some configs).","commonSituations":"Long-lived scripts/CLI sessions holding a token for days; TokenExpiresIn lowered in config causing old tokens to lapse; server clock ahead of client.","solutions":["Catch this error and re-authenticate to get a new token (standard token refresh flow)","Increase TokenExpiresIn in config.json for unattended clients, or set 0 if you want non-expiring tokens","For automation, wrap API calls in a helper that transparently re-logins on 'token is expired'"],"exampleFix":"// before\nresp, err := doRequest(token) // errors after 48h\n\n// after\nresp, err := doRequest(token)\nif err != nil && strings.Contains(err.Error(), \"expired\") {\n    token = login(username, password)\n    resp, err = doRequest(token)\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"claims, err := common.ParseToken(tok)\nif err != nil && strings.Contains(err.Error(), \"expired\") {\n\ttok = login(user, pass)\n\tclaims, err = common.ParseToken(tok)\n}","preventionTips":["Wrap API clients with automatic re-login on expiry","Set TokenExpiresIn appropriate to your client lifetime"],"tags":["jwt","auth","expiry","token"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}