{"record":{"id":"d39702389373d61e","repo":"vxcontrol/pentagi","slug":"token-is-invalid","errorCode":null,"errorMessage":"token is invalid","messagePattern":"token is invalid","errorType":"http","errorClass":null,"httpStatus":401,"severity":"error","filePath":"backend/pkg/server/auth/auth_middleware.go","lineNumber":218,"sourceCode":"\t}\n\n\tif !strings.HasPrefix(authHeader, \"Bearer \") {\n\t\treturn authResultSkip, errors.New(\"bearer scheme must be used\")\n\t}\n\ttoken := authHeader[7:]\n\tif token == \"\" {\n\t\treturn authResultSkip, errors.New(\"token can't be empty\")\n\t}\n\n\t// skip validation if using default salt (for backward compatibility)\n\tif p.globalSalt == \"\" || p.globalSalt == \"salt\" {\n\t\treturn authResultSkip, errors.New(\"token validation disabled with default salt\")\n\t}\n\n\t// try to validate as API token first (new format with JWT signing key)\n\tapiClaims, apiErr := ValidateAPIToken(token, p.globalSalt)\n\tif apiErr != nil {\n\t\treturn authResultFail, errors.New(\"token is invalid\")\n\t}\n\n\t// check token status and get privileges through cache\n\tstatus, privileges, err := p.tokenCache.GetStatus(apiClaims.TokenID)\n\tif err != nil {\n\t\tif errors.Is(err, gorm.ErrRecordNotFound) {\n\t\t\treturn authResultFail, errors.New(\"token not found in database\")\n\t\t}\n\t\treturn authResultFail, fmt.Errorf(\"error checking token status: %w\", err)\n\t}\n\tif status != models.TokenStatusActive {\n\t\treturn authResultFail, errors.New(\"token has been revoked\")\n\t}\n\n\t// Verify user hash matches database\n\tdbHash, userStatus, err := p.userCache.GetUserHash(apiClaims.UID)\n\tif err != nil {\n\t\tif errors.Is(err, gorm.ErrRecordNotFound) {","sourceCodeStart":200,"sourceCodeEnd":236,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/server/auth/auth_middleware.go#L200-L236","documentation":"ValidateAPIToken failed to verify the bearer token's JWT signature, claims, or expiry using the server's global salt, so the middleware returns authResultFail with this generic invalid-token error. The token is syntactically present but cryptographically or structurally unacceptable.","triggerScenarios":"Sending an expired API token; a token signed with a different/old salt (salt rotated after token issuance); a corrupted or truncated token string; a cookie/session JWT or token from another installation passed as an API token.","commonSituations":"Salt changed in config after tokens were issued; copying a token with surrounding quotes or whitespace; old tokens surviving a server migration; clock skew making an unexpired-on-client token expired-on-server.","solutions":["Generate a fresh API token from the settings UI and update the client.","Check the token was copied exactly — no quotes, whitespace, or line breaks.","If the salt was rotated, reissue all tokens; old ones are permanently invalid.","Confirm server time is correct (NTP) if tokens appear to expire early."],"exampleFix":"// before\nconst token = JSON.parse(fs.readFileSync(\"token.json\")); // object, not string\n\n// after\nconst token = fs.readFileSync(\"token.txt\", \"utf8\").trim();","handlingStrategy":"retry","validationCode":"const token = loadToken();\nif (!/^[A-Za-z0-9\\-_=.]+$/.test(token)) {\n  throw new Error(\"API token is malformed (check for quotes/whitespace/truncation)\");\n}","typeGuard":"function looksLikeJwt(t: string): boolean {\n  const p = t.split(\".\");\n  return p.length === 3 && p.every((s) => s.length > 0);\n}","tryCatchPattern":"try {\n  return await call(token);\n} catch (e) {\n  if (is401(e) && /token is invalid/i.test(e.message)) {\n    const fresh = await mintNewToken(); // salt may have rotated; old tokens are dead\n    return await call(fresh);\n  }\n  throw e;\n}","preventionTips":["Rotate salt only together with a planned reissue of all API tokens.","Copy tokens programmatically (no clipboard/quotes); trim on load.","Sync server clocks with NTP and set token TTLs with skew margin."],"tags":["authentication","jwt","go","api-token"],"backgroundTag":"jwt-token-invalid","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}