Aller au contenu principal

Autorisation granulaire (FGA)

remarque

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 FGA
Lien 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.

Configuration
Lien 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 ressources
Lien 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, le resourceId d'un thread)
  • requestContext : le contexte de requête actuel, pour une résolution avancée du tenant
  • metadata : 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 autorisations
Lien 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ées
Lien 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 route
Lien 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'application
Lien 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 vieAutorisation contrôléeType de ressourceID de la ressource
Exécution d'un agent (generate, stream)agents:executeagentagentId
Routes HTTP intégrées d'exécution des workflows et Workflow.execute()workflows:executeworkflowworkflowId
Exécution d'un outil autonometools:executetooltoolName
Exécution d'un outil par un agenttools:executetool${agentId}:${toolName}
Exécution d'un outil MCPtools:executetool par défaut, ou la redéfinition fga.resourceMapping au niveau du serveurJSON.stringify([serverName, toolName]) par défaut, ou l'ID calculé au niveau du serveur
Accès aux threads et à la mémoirememory:read, memory:write, memory:deletethreadthreadId
Routes de ressources stockéesAutorisation de la ressource stockée correspondant à l'action de la routeType de ressource stockéeID de l'enregistrement de la route, ou portée de la ressource stockée pour les routes de collection
Routes HTTP de ressourcesConfiguré pour chaque routeConfiguré pour chaque routeConfiguré 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
}
}
remarque

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ème
Lien 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 :

  • true ou { actorKind: 'system' } identifie un acteur système anonyme.
  • La forme objet peut également contenir agentId, permissions et scope afin 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 confiance
Lien direct vers Exigences de confiance

Le signal d'acteur est une entrée de confiance ; construisez-le donc côté serveur :

  • Traitez actor comme 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 des defaultOptions de 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 actor des 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 organizationId fourni par le client dans le contexte de requête, et le chemin d'un acteur de confiance exige qu'un organizationId soit 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 organizationId de confiance. Il ne vérifie pas que actor.agentId appartient à cette organisation. Lorsque cette relation est importante, vérifiez-la dans requireActor à l'aide des données de référence du fournisseur.
  • Considérez actor.permissions comme 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é par agentId, 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.