# Fine-Grained Authorization (FGA) Fine-Grained Authorization (FGA) adds resource-level permission checks to your Mastra application. While RBAC answers "can this role do this action?", FGA answers **"can this user do this action on this specific resource?"** ## When to use FGA FGA is designed for multi-tenant B2B products where permissions are contextual: - A user might be an **admin** of Team A but only a **member** of Team B - Thread access should be limited to the user's own organization - Workflow execution should be scoped to a specific team or project - Tool access depends on the user's relationship to a resource ## Configuration Configure FGA in your Mastra server config alongside authentication and RBAC: ```typescript import { Mastra } from '@mastra/core/mastra'; import { MastraFGAPermissions } from '@mastra/core/auth/ee'; import { MastraAuthWorkos, MastraFGAWorkos } from '@mastra/auth-workos'; const mastra = new Mastra({ server: { auth: new MastraAuthWorkos({ /* ... */ fetchMemberships: true, }), fga: new MastraFGAWorkos({ resourceMapping: { agent: { fgaResourceType: 'team', deriveId: (ctx) => ctx.user.teamId }, workflow: { fgaResourceType: 'team', deriveId: (ctx) => ctx.user.teamId }, thread: { fgaResourceType: 'workspace-thread', deriveId: ({ resourceId }) => resourceId }, }, permissionMapping: { [MastraFGAPermissions.AGENTS_EXECUTE]: 'manage-workflows', [MastraFGAPermissions.WORKFLOWS_EXECUTE]: 'manage-workflows', [MastraFGAPermissions.MEMORY_READ]: 'read', [MastraFGAPermissions.MEMORY_WRITE]: 'update', }, }), }, }); ``` When using `MastraFGAWorkos`, set `fetchMemberships: true` on `MastraAuthWorkos`. WorkOS FGA checks need the user's organization memberships to resolve the correct membership ID for authorization. Use `thread` as the resource-mapping key for memory authorization. `MastraFGAWorkos` still accepts the legacy alias `memory`, but new configs should prefer `thread`. ### Resource mapping The `resourceMapping` tells Mastra how to resolve FGA resource types and IDs from request context. Keys are Mastra resource types, values define the FGA resource type and how to derive the ID: ```typescript resourceMapping: { // When checking "can user execute agent X?", resolve the FGA resource // as the user's team (type: 'team', id: user.teamId) agent: { fgaResourceType: 'team', deriveId: (ctx) => ctx.user.teamId, }, } ``` `deriveId()` receives: - `user` — the authenticated user - `resourceId` — the owning Mastra resource ID when available (for example, a thread's `resourceId`) - `requestContext` — the current request context for advanced tenant resolution Return `undefined` from `deriveId()` to fall back to the original Mastra resource ID. For thread and memory checks, Mastra still passes the raw `threadId` as the resource being checked, but it also forwards the thread's owning `resourceId` into `deriveId()`. This lets you map thread permissions to composite tenant IDs such as `userId-teamId-orgId`. ### Permission mapping The `permissionMapping` translates Mastra's internal permission strings to your FGA provider's permission slugs: ```typescript import { MastraFGAPermissions } from '@mastra/core/auth/ee'; permissionMapping: { [MastraFGAPermissions.AGENTS_EXECUTE]: 'manage-workflows', // Mastra permission -> WorkOS permission slug [MastraFGAPermissions.MEMORY_READ]: 'read', } ``` If no mapping exists for a permission, the original string is passed through. ## Enforcement points When an FGA provider is configured, Mastra automatically checks authorization at these lifecycle points: | Lifecycle point | Permission checked | Resource | | -------------------------------------- | ---------------------------------------------- | -------------------------------------- | | Agent execution (`generate`, `stream`) | `agents:execute` | `{ type: 'agent', id: agentId }` | | Workflow execution | `workflows:execute` | `{ type: 'workflow', id: workflowId }` | | Tool execution | `tools:execute` | `{ type: 'tool', id: toolName }` | | Thread/memory access | `memory:read`, `memory:write`, `memory:delete` | `{ type: 'thread', id: threadId }` | | MCP tool execution | `tools:execute` | `{ type: 'tool', id: toolName }` | | HTTP routes (opt-in) | Configured per route | Configured per route | All checks are **no-ops when FGA is not configured**, maintaining backward compatibility. ## Custom FGA provider Implement `IFGAProvider` to use any FGA backend: ```typescript import { FGADeniedError } from '@mastra/core/auth/ee' import type { FGACheckParams, IFGAProvider, MastraFGAPermissionInput } from '@mastra/core/auth/ee' class MyFGAProvider implements IFGAProvider { async check(user: any, params: FGACheckParams): Promise { // Your authorization logic return true } async require(user: any, params: FGACheckParams): Promise { const allowed = await this.check(user, params) if (!allowed) { throw new FGADeniedError(user, params.resource, params.permission) } } async filterAccessible( user: any, resources: T[], resourceType: string, permission: MastraFGAPermissionInput, ): Promise { // Filter resources the user can access return resources } } ``` ## Related - [Authentication overview](https://mastra.ai/docs/server/auth) - [WorkOS authentication](https://mastra.ai/docs/server/auth/workos)