{"record":{"id":"2c999f515437cdae","repo":"mastra-ai/mastra","slug":"fga-authorization-denied-authenticated-user-is-re","errorCode":null,"errorMessage":"FGA authorization denied: authenticated user is required","messagePattern":"FGA authorization denied: authenticated user is required","errorType":"http","errorClass":"HTTPException","httpStatus":403,"severity":"error","filePath":"packages/server/src/server/handlers/utils.ts","lineNumber":153,"sourceCode":"  permission = MastraFGAPermissions.MEMORY_READ,\n}: {\n  mastra: any;\n  requestContext?: RequestContext;\n  threadId: string;\n  thread?: { resourceId?: string | null } | null;\n  effectiveResourceId?: string;\n  permission?: MastraFGAPermissionInput;\n}): Promise<void> {\n  await validateThreadOwnership(thread, effectiveResourceId);\n\n  const fgaProvider = mastra?.getServer?.()?.fga;\n  if (!fgaProvider) {\n    return;\n  }\n\n  const user = requestContext?.get('user');\n  if (!user || typeof user !== 'object') {\n    throw new HTTPException(403, { message: 'FGA authorization denied: authenticated user is required' });\n  }\n\n  await MastraMemory.checkThreadFGA({\n    mastra,\n    user: user as { id: string; [key: string]: unknown },\n    threadId,\n    resourceId: thread?.resourceId ?? effectiveResourceId,\n    requestContext,\n    permission,\n  });\n}\n\n/**\n * Validates that a workflow run belongs to the specified resourceId.\n * Throws 403 if the run exists but belongs to a different resource.\n */\nexport async function validateRunOwnership(\n  run: { resourceId?: string | null } | null | undefined,","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/server/src/server/handlers/utils.ts#L135-L171","documentation":"When a Fine-Grained Authorization (FGA) provider is configured, enforceThreadAccess requires an authenticated user object in requestContext to evaluate thread permissions. If no user is present (auth disabled or hook did not populate it), the check cannot run, so it fails closed with this 403 rather than granting access.","triggerScenarios":"Calling generate/stream/list-suspended/resume routes on a Mastra server with an FGA provider configured, where requestContext has no `user` object — typically because authentication is disabled or the auth hook failed to attach the user.","commonSituations":"Enabling FGA (e.g. OpenFGA/SpiceDB integration) while leaving server auth disabled; a custom auth plugin that validates tokens but never sets requestContext.set('user', ...); calling routes with an unauthenticated internal service client.","solutions":["Enable and correctly configure server authentication so a user object is attached to requestContext for every request.","In custom auth, ensure you call requestContext.set('user', { id, ... }) after validating credentials.","Use an authenticated client (valid token/credentials) when calling FGA-protected routes.","Temporarily disable the FGA provider in non-production if unauthenticated internal calls are required."],"exampleFix":"// before\n// custom auth verifies token but never sets user\nif (token) next();\n// after\nconst user = await verifyToken(token);\nrequestContext.set('user', { id: user.sub, email: user.email });\nnext();","handlingStrategy":"validation","validationCode":"function requireAuthenticatedUser(requestContext: { get: (k: string) => unknown }): asserts requestContext is { get: (k: 'user') => { id: string } } {\n  const user = requestContext.get('user');\n  if (!user || typeof user !== 'object' || typeof (user as any).id !== 'string') {\n    throw new Error('FGA-protected routes require an authenticated user attached to requestContext');\n  }\n}","typeGuard":"function hasUser(ctx: { get: (k: string) => unknown }): boolean {\n  const u = ctx.get('user');\n  return !!u && typeof u === 'object' && typeof (u as any).id === 'string';\n}","tryCatchPattern":"try {\n  const res = await fetch('/api/agents/assistant/generate', { method: 'POST', headers: authHeaders, body: JSON.stringify(payload) });\n  if (res.status === 403 && (await res.text()).includes('FGA authorization denied')) {\n    throw new Error('Ensure server auth is enabled and the user is attached to requestContext when FGA is configured');\n  }\n  return await res.json();\n} catch (e) { throw e; }","preventionTips":["If you enable an FGA provider, always enable server auth with it.","In custom auth plugins, call requestContext.set('user', { id, ... }) after token validation.","Use authenticated clients for all calls to FGA-protected routes.","Add an integration test asserting the user is present in requestContext."],"tags":["http-403","fga","authorization","authentication"],"backgroundTag":"fga-authorization-denied","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}