{"record":{"id":"6620cda7b81d8393","repo":"multica-ai/multica","slug":"missing-authorization","errorCode":null,"errorMessage":"missing authorization","messagePattern":"missing authorization","errorType":"http","errorClass":null,"httpStatus":401,"severity":"error","filePath":"server/internal/middleware/auth.go","lineNumber":52,"sourceCode":"// local DB. When nil (Fleet URL unset) mcn_ tokens are rejected at the\n// prefix branch — we don't fall through to the mul_ / JWT paths, since\n// an mcn_ string is by construction not a valid mul_ PAT or JWT.\nfunc Auth(queries *db.Queries, patCache *auth.PATCache, cloudPAT *auth.CloudPATVerifier) func(http.Handler) http.Handler {\n\treturn func(next http.Handler) http.Handler {\n\t\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t// X-Actor-Source is server-set only — any value supplied by\n\t\t\t// the client is untrusted and discarded before the auth\n\t\t\t// branches run. Only the mat_ branch below re-sets it. This\n\t\t\t// is what prevents a client from sending a normal mul_ PAT\n\t\t\t// plus a forged `X-Actor-Source: member` (or anything else)\n\t\t\t// to convince a downstream handler that its request came\n\t\t\t// from a non-task-token path.\n\t\t\tr.Header.Del(\"X-Actor-Source\")\n\n\t\t\ttokenString, fromCookie := extractToken(r)\n\t\t\tif tokenString == \"\" {\n\t\t\t\tslog.Debug(\"auth: no token found\", \"path\", r.URL.Path)\n\t\t\t\thttp.Error(w, `{\"error\":\"missing authorization\"}`, http.StatusUnauthorized)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Cookie-based auth requires CSRF validation for state-changing methods.\n\t\t\tif fromCookie && !auth.ValidateCSRF(r) {\n\t\t\t\tslog.Debug(\"auth: CSRF validation failed\", \"path\", r.URL.Path)\n\t\t\t\thttp.Error(w, `{\"error\":\"CSRF validation failed\"}`, http.StatusForbidden)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Agent task token: \"mat_\" prefix. Minted by the server at\n\t\t\t// task-claim time and injected by the daemon into the agent\n\t\t\t// process. Authoritative for actor identity — the bound\n\t\t\t// (user_id, agent_id, task_id, workspace_id) triple is\n\t\t\t// written into request headers here, OVERRIDING whatever the\n\t\t\t// client sent, so a downstream actor-resolver cannot be\n\t\t\t// tricked by a client that strips or forges X-Agent-ID /\n\t\t\t// X-Task-ID. Human-only endpoints (e.g. agent env","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/multica-ai/multica/blob/2c0912b6ec764b373d44eeea1e80f0d9f11ab417/server/internal/middleware/auth.go#L34-L70","documentation":"HTTP 401 from the auth middleware when extractToken finds no bearer token and no auth cookie on the request. This is the first gate of the authentication chain: every subsequent branch (mat_ task tokens, mcn_ cloud PATs, mul_ PATs, JWTs) requires a non-empty token string, so an absent token fails here with 'missing authorization'. The path is logged at Debug level.","triggerScenarios":"Calling any API endpoint behind the auth middleware without an Authorization: Bearer <token> header and without the auth cookie; sending 'Bearer' with an empty value; a client that stores the token in a differently-named header.","commonSituations":"Forgot to attach the PAT in a curl/script; browser session cookie expired and was cleared; proxy strips the Authorization header; frontend not yet wired to send credentials.","solutions":["Attach a valid token: Authorization: Bearer <PAT or mat_ task token> or the auth cookie.","If using cookie auth in a browser, log in again to obtain a fresh session cookie.","Verify no intermediate proxy or load balancer strips the Authorization header.","Check the client's token-loading code actually reads the env/config where the token is stored."],"exampleFix":"# before\ncurl :8080/api/issues\n# → 401 {\"error\":\"missing authorization\"}\n\n# after\ncurl :8080/api/issues -H \"Authorization: Bearer $PAT\"","handlingStrategy":"validation","validationCode":"if strings.TrimSpace(token) == \"\" {\n    return errors.New(\"no token configured; set PAT env var\")\n}\nreq.Header.Set(\"Authorization\", \"Bearer \"+token)","typeGuard":"func hasToken(tok string) bool { return strings.TrimSpace(tok) != \"\" }","tryCatchPattern":"resp, err := client.Do(req)\nif err == nil && resp.StatusCode == 401 {\n    // 401 missing authorization: load/refresh credentials, retry once; else prompt login\n}","preventionTips":["Centralize token injection in one HTTP client wrapper/middleware.","Fail fast at startup if no credential is configured.","Handle 401 by refreshing credentials, never by silently retrying."],"tags":["authentication","http-401","middleware","authorization"],"backgroundTag":null,"analyzedSha":"2c0912b6ec764b373d44eeea1e80f0d9f11ab417","analyzedAt":"2026-08-15T13:25:18.241Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}