{"record":{"id":"64cbe51f0165af62","repo":"AlistGo/alist","slug":"that-s-not-even-a-token","errorCode":null,"errorMessage":"that's not even a token","messagePattern":"that's not even a token","errorType":"http","errorClass":null,"httpStatus":401,"severity":"error","filePath":"server/common/auth.go","lineNumber":51,"sourceCode":"\ttokenString, err = token.SignedString(SecretKey)\n\tif err != nil {\n\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","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/server/common/auth.go#L33-L69","documentation":"ParseToken maps jwt.ValidationErrorMalformed to this message: the string is not a well-formed JWT at all — it could not be parsed into three dot-separated base64 segments with valid structure.","triggerScenarios":"Passing \"abc\", a base64 blob without dots, a truncated token, or a non-JWT string (e.g. a raw API key or sign string) to ParseToken.","commonSituations":"Authorization header parsing bug that passes \"Bearer\" without the token; client sends the wrong credential type; token mangled by a proxy or copy/paste truncation.","solutions":["Ensure the client sends a JWT issued by /api/auth/login in the Authorization header","Split the header correctly (strip the \"Bearer \" prefix, handle case) before calling ParseToken","Validate shape first: token must have 3 dot-separated, non-empty segments"],"exampleFix":"// before\ntokenString := strings.TrimPrefix(authHeader, \"bearer \") // wrong case handling left 'Bearer '\n\n// after\nparts := strings.SplitN(authHeader, \" \", 2)\nif len(parts) != 2 || !strings.EqualFold(parts[0], \"Bearer\") {\n    return errors.New(\"malformed authorization header\")\n}\ntokenString := parts[1]","handlingStrategy":"validation","validationCode":"func looksLikeJWT(s string) bool {\n\tp := strings.Split(s, \".\")\n\treturn len(p) == 3 && p[0] != \"\" && p[1] != \"\" && p[2] != \"\"\n}","typeGuard":"func isJWT(s string) bool { return looksLikeJWT(s) }","tryCatchPattern":"_, err := common.ParseToken(tok)\nif err != nil && strings.Contains(err.Error(), \"not even a token\") {\n\t// reject the credential outright; do not retry\n}","preventionTips":["Parse the Authorization header strictly (Bearer scheme, case-insensitive)","Shape-check tokens before hitting the JWT parser"],"tags":["jwt","auth","parsing","token"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}