Autorisation granulaire (FGA)
L'autorisation granulaire fait partie de l'édition Entreprise de Mastra. Les déploiements en production nécessitent une licence EE valide. Contactez l'équipe commerciale pour en savoir plus.
L'autorisation granulaire (FGA) ajoute à votre application Mastra des contrôles d'autorisation au niveau des ressources. Alors que le RBAC répond à la question « ce rôle peut-il effectuer cette action ? », la FGA répond à « cet utilisateur peut-il effectuer cette action sur cette ressource précise ? »
Quand utiliser la FGALien direct vers Quand utiliser la FGA
La FGA est conçue pour les produits B2B mutualisés, dans lesquels les autorisations dépendent du contexte :
- Un utilisateur peut être administrateur de l'équipe A, mais seulement membre de l'équipe B.
- L'accès aux threads doit être limité à l'organisation de l'utilisateur.
- L'exécution d'un workflow doit être restreinte à une équipe ou à un projet précis.
- L'accès aux outils dépend de la relation entre l'utilisateur et une ressource.
ConfigurationLien direct vers Configuration
Configurez la FGA dans la configuration de votre serveur Mastra, aux côtés de l'authentification et du RBAC :
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,
mapUserToResourceId: user => user.teamId,
}),
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',
},
}),
storedResources: {
scope: true,
},
},
});
Lorsque vous utilisez MastraFGAWorkos, définissez fetchMemberships: true sur MastraAuthWorkos. Les contrôles FGA de WorkOS ont besoin des appartenances de l'utilisateur aux organisations afin de déterminer l'ID d'appartenance approprié pour l'autorisation.
Utilisez thread comme clé de mappage de ressource pour l'autorisation de la mémoire. MastraFGAWorkos accepte toujours l'ancien alias memory, mais les nouvelles configurations doivent privilégier thread.
Lorsque server.fga est configuré, Mastra applique la FGA aux actions protégées. Si aucune personne authentifiée n'est associée à une action protégée, Mastra refuse cette action. Si server.fga n'est pas configuré, ces contrôles FGA sont ignorés et Mastra conserve le comportement antérieur.
Mappage des ressourcesLien direct vers Mappage des ressources
resourceMapping indique à Mastra comment déterminer les types et les ID des ressources FGA à partir du contexte de requête. Les clés correspondent aux types de ressources Mastra ; les valeurs définissent le type de ressource FGA et la manière de calculer son ID :
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() reçoit :
user: l'utilisateur authentifiéresourceId: l'ID de la ressource Mastra propriétaire lorsqu'il est disponible (par exemple, leresourceIdd'un thread)requestContext: le contexte de requête actuel, pour une résolution avancée du tenantmetadata: les métadonnées propres au fournisseur pour l'action tentée
Renvoyez undefined depuis deriveId() pour utiliser à défaut l'ID d'origine de la ressource Mastra.
Pour les contrôles portant sur les threads et la mémoire, Mastra transmet toujours le threadId brut comme ressource à vérifier, mais fournit également le resourceId propriétaire du thread à deriveId(). Vous pouvez ainsi associer les autorisations d'un thread à des ID de tenant composites, comme userId-teamId-orgId.
Mappage des autorisationsLien direct vers Mappage des autorisations
permissionMapping traduit les chaînes d'autorisation internes de Mastra en slugs d'autorisation utilisés par votre fournisseur FGA :
import { MastraFGAPermissions } from '@mastra/core/auth/ee';
permissionMapping: {
[MastraFGAPermissions.AGENTS_EXECUTE]: 'manage-workflows', // Mastra permission -> WorkOS permission slug
[MastraFGAPermissions.MEMORY_READ]: 'read',
}
Si aucun mappage n'existe pour une autorisation, la chaîne d'origine est transmise telle quelle.
Utilisez validatePermissions() pour valider au démarrage l'ensemble des autorisations que Mastra est susceptible d'émettre. Cette validation est utile lorsqu'un fournisseur exige que chaque autorisation Mastra possède un slug explicite propre au fournisseur.
Délimitation des ressources stockéesLien direct vers Délimitation des ressources stockées
La FGA autorise l'accès à une ressource, mais ne filtre pas automatiquement les enregistrements conservés dans un stockage partagé. Activez la délimitation des ressources stockées lorsque les API intégrées correspondantes sont utilisées dans une application mutualisée.
const mastra = new Mastra({
server: {
auth: new MastraAuthWorkos({
/* ... */
mapUserToResourceId: user => user.teamId,
}),
storedResources: {
scope: true,
},
},
});
Avec scope: true, Mastra lit MASTRA_RESOURCE_ID_KEY dans le contexte de requête. mapUserToResourceId() définit cette valeur après l'authentification. Les gestionnaires de ressources stockées conservent la portée dans les métadonnées de l'enregistrement et filtrent selon cette portée les opérations de listage, de lecture, de mise à jour, de publication et de suppression.
Utilisez un objet lorsque la portée nécessite une logique de requête personnalisée :
storedResources: {
scope: {
metadataKey: 'teamId',
resolve: ({ user }) => user.teamId,
requireScope: true,
},
},
Si requireScope vaut true ou n'est pas défini, les routes de ressources stockées délimitées échouent lorsqu'aucune portée ne peut être déterminée.
Couverture des politiques de routeLien direct vers Couverture des politiques de route
Mastra inclut des métadonnées FGA au niveau des routes pour les routes de ressources intégrées, notamment celles des agents, workflows, outils, outils MCP, threads de mémoire, réponses, conversations et ressources stockées. La couverture des routes de ressources stockées comprend /stored/agents, /stored/mcp-clients, /stored/prompt-blocks, /stored/scorers, /stored/skills et /stored/workspaces. Une route est contrôlée si elle possède des métadonnées fga au niveau de la route, si Mastra peut en déduire les métadonnées intégrées, ou si le fournisseur fournit des métadonnées avec resolveRouteFGA().
Pour refuser les routes protégées dont les métadonnées FGA ne peuvent pas être déterminées, configurez la couverture des politiques de route sur le fournisseur FGA :
const fga = new MastraFGAWorkos({
resourceMapping: {
project: { fgaResourceType: 'project' },
},
permissionMapping: {
'projects:read': 'read',
},
requireForProtectedRoutes: true,
auditProtectedRoutes: 'warn',
validatePermissions: async permissions => {
// Throw if a Mastra permission is missing from permissionMapping.
},
});
Définissez auditProtectedRoutes: 'error' afin de faire échouer le démarrage lorsque des routes protégées ne disposent pas de métadonnées FGA intégrées. Si requireForProtectedRoutes est activé, Mastra consigne par défaut cet audit sous forme d'avertissement.
Pour les routes personnalisées, privilégiez les métadonnées fga au niveau de la route. La politique d'autorisation reste ainsi définie à proximité de celle-ci :
import { createRoute } from '@mastra/server/server-adapter';
export const getProjectRoute = createRoute({
method: 'GET',
path: '/projects/:projectId',
responseType: 'json',
requiresAuth: true,
fga: {
resourceType: 'project',
resourceIdParam: 'projectId',
permission: 'projects:read',
},
handler: async () => {
return { project: null };
},
});
N'utilisez resolveRouteFGA() que lorsque les métadonnées doivent être calculées de manière centralisée à partir de la route, de ses paramètres ou du contexte de requête. Une table de routes s'adapte mieux à la montée en charge que des contrôles fondés sur le préfixe d'une chaîne :
import type { FGARouteConfig, FGARouteResolver } from '@mastra/core/auth/ee';
const routeFGA = {
'GET /billing/:accountId': {
resourceType: 'account',
resourceIdParam: 'accountId',
permission: 'billing:read',
},
} satisfies Record<string, FGARouteConfig>;
const resolveRouteFGA: FGARouteResolver = ({ route }) => routeFGA[`${route.method} ${route.path}`];
const fga = new MastraFGAWorkos({
/* ... */
resolveRouteFGA,
});
Points d'applicationLien direct vers Points d'application
Lorsqu'un fournisseur FGA est configuré, Mastra contrôle automatiquement les autorisations aux points suivants du cycle de vie :
| Point du cycle de vie | Autorisation contrôlée | Type de ressource | ID de la ressource |
|---|---|---|---|
Exécution d'un agent (generate, stream) | agents:execute | agent | agentId |
Routes HTTP intégrées d'exécution des workflows et Workflow.execute() | workflows:execute | workflow | workflowId |
| Exécution d'un outil autonome | tools:execute | tool | toolName |
| Exécution d'un outil par un agent | tools:execute | tool | ${agentId}:${toolName} |
| Exécution d'un outil MCP | tools:execute | tool par défaut, ou la redéfinition fga.resourceMapping au niveau du serveur | JSON.stringify([serverName, toolName]) par défaut, ou l'ID calculé au niveau du serveur |
| Accès aux threads et à la mémoire | memory:read, memory:write, memory:delete | thread | threadId |
| Routes de ressources stockées | Autorisation de la ressource stockée correspondant à l'action de la route | Type de ressource stockée | ID de l'enregistrement de la route, ou portée de la ressource stockée pour les routes de collection |
| Routes HTTP de ressources | Configuré pour chaque route | Configuré pour chaque route | Configuré pour chaque route |
Pour les serveurs MCP protégés par OAuth, les transports HTTP MCP transmettent les données authentifiées sous la forme extra.authInfo. Si un MCPServer est enregistré sur une instance Mastra utilisant la FGA, configurez mapAuthInfoToUser afin que Mastra puisse définir requestContext.get('user') avant de contrôler tools/list et tools/call. Utilisez l'option fga au niveau du serveur lorsque les contrôles des outils MCP nécessitent un mappage de ressources ou d'autorisations différent de celui des outils internes des agents et des workflows. Consultez le contexte d'authentification de MCPServer.
Dans cette version, les appels directs du SDK à createRun().start(), resume() ou restart() ne sont pas contrôlés indépendamment par le cœur de la FGA. Effectuez ces appels depuis une route protégée ou sécurisez-les dans le code de l'application. Transmettez un requestContext contenant un utilisateur authentifié lorsque vous appelez directement des points d'entrée protégés.
Les contrôles effectués par le cœur sur les agents, les workflows internes, les outils et la mémoire transmettent également le requestContext et les métadonnées de l'action au fournisseur FGA. Les contrôles de route transmettent le requestContext. Ceux des threads transmettent le resourceId propriétaire lorsqu'il est disponible.
Fournisseur FGA personnaliséLien direct vers Fournisseur FGA personnalisé
Implémentez IFGAProvider pour utiliser n'importe quel backend FGA :
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<boolean> {
// Your authorization logic
return true
}
async require(user: any, params: FGACheckParams): Promise<void> {
const allowed = await this.check(user, params)
if (!allowed) {
throw new FGADeniedError(user, params.resource, params.permission)
}
}
async filterAccessible<T extends { id: string }>(
user: any,
resources: T[],
resourceType: string,
permission: MastraFGAPermissionInput,
): Promise<T[]> {
// Filter resources the user can access
return resources
}
}
Consultez la référence de IFGAProvider pour découvrir l'ensemble des méthodes et paramètres, ainsi que le type ActorSignal.
Acteurs systèmeLien direct vers Acteurs système
Les agents autonomes et planifiés s'exécutent sans utilisateur final. Marquez ces appels à l'aide d'un signal d'acteur afin que la FGA puisse les distinguer des requêtes utilisateur :
trueou{ actorKind: 'system' }identifie un acteur système anonyme.- La forme objet peut également contenir
agentId,permissionsetscopeafin d'identifier et de restreindre l'agent qui agit.
Par défaut, après un contrôle de la portée du tenant, un acteur de confiance contourne le contrôle require() centré sur l'utilisateur. Pour appliquer le principe du moindre privilège à chaque agent, implémentez la méthode facultative requireActor sur votre fournisseur. Elle reçoit l'acteur ainsi que les mêmes FGACheckParams que require, puis lève FGADeniedError en cas de refus. Lorsque votre fournisseur n'implémente pas requireActor, le contournement accordé aux acteurs de confiance reste en vigueur ; l'ajout de cette méthode est donc rétrocompatible.
import { FGADeniedError } from '@mastra/core/auth/ee'
import type { ActorSignal, FGACheckParams, IFGAProvider } from '@mastra/core/auth/ee'
class MyFGAProvider implements IFGAProvider {
// ...check, require, filterAccessible...
async requireActor(actor: ActorSignal, params: FGACheckParams): Promise<void> {
const agentId = actor === true ? undefined : actor.agentId
// Resolve the agent's real grants from a trusted source keyed by agentId.
const granted = await this.grantsForAgent(agentId)
const required = Array.isArray(params.permission) ? params.permission : [params.permission]
if (!required.some(permission => granted.includes(permission))) {
throw new FGADeniedError(null, params.resource, params.permission)
}
}
}
Exigences de confianceLien direct vers Exigences de confiance
Le signal d'acteur est une entrée de confiance ; construisez-le donc côté serveur :
- Traitez
actorcomme un signal propre à chaque appel. Les workflows durables le transmettent au démarrage d'une exécution, mais ne rétablissent pas l'acteur initial lors d'une reprise. Transmettez-le explicitement à chaque reprise de confiance, ou déterminez un nouvel acteur à l'aide desdefaultOptionsde l'agent. En l'absence d'acteur actuel, l'autorisation utilisateur s'applique et l'absence d'utilisateur entraîne un refus par défaut. - Mastra retire
actordes options d'exécution prises en charge par ses routes HTTP intégrées pour les agents. Définissez-le dans le code côté serveur, par exemple dans une tâche planifiée ou un workflow, et jamais à partir d'une entrée client. - Établissez la portée du tenant côté serveur. Les routes HTTP intégrées des agents ignorent tout
organizationIdfourni par le client dans le contexte de requête, et le chemin d'un acteur de confiance exige qu'unorganizationIdsoit défini. - La reprise durable conserve son comportement actuel de récupération et de fusion du contexte de requête. Cela ne rend pas un acteur persistant digne de confiance pour un segment ultérieur du workflow.
- Le contrôle de la portée du tenant confirme l'existence d'un
organizationIdde confiance. Il ne vérifie pas queactor.agentIdappartient à cette organisation. Lorsque cette relation est importante, vérifiez-la dansrequireActorà l'aide des données de référence du fournisseur. - Considérez
actor.permissionscomme une déclaration non vérifiée. Déterminez les autorisations de référence à partir d'une source de confiance. Un fournisseur qui applique le principe du moindre privilège récupère les autorisations de référence de l'agent auprès d'une source fiable, par exemple un manifeste ou votre backend FGA indexé paragentId, au lieu de faire confiance aux valeurs fournies directement. - Dès qu'un fournisseur implémente
requireActor, les erreurs levées par cette méthode interrompent l'exécution. Mastra ne se rabat pas sur une autorisation limitée à l'organisation.