Aller au contenu principal

Middleware

Les serveurs Mastra peuvent exécuter des fonctions middleware personnalisées avant ou après l'appel d'un gestionnaire de route d'API. Cela permet notamment de gérer l'authentification, la journalisation, l'injection d'un contexte propre à la requête ou l'ajout d'en-têtes CORS.

Un middleware reçoit le Context (c) de Hono ainsi qu'une fonction next. S'il renvoie une Response, le traitement de la requête est interrompu. L'appel à next() poursuit le traitement avec le middleware ou le gestionnaire de route suivant.

import { Mastra } from '@mastra/core'

export const mastra = new Mastra({
server: {
middleware: [
{
handler: async (c, next) => {
// Example: Add authentication check
const authHeader = c.req.header('Authorization')
if (!authHeader) {
return new Response('Unauthorized', { status: 401 })
}

await next()
},
path: '/api/*',
},
// Add a global request logger
async (c, next) => {
console.log(`${c.req.method} ${c.req.url}`)
await next()
},
],
},
})

Pour associer un middleware à une seule route, transmettez l'option middleware à registerApiRoute :

registerApiRoute('/my-custom-route', {
method: 'GET',
middleware: [
async (c, next) => {
console.log(`${c.req.method} ${c.req.url}`)
await next()
},
],
handler: async c => {
const mastra = c.get('mastra')
return c.json({ message: 'Hello, world!' })
},
})

Exemples courants
Lien direct vers Exemples courants

Utiliser RequestContext
Lien direct vers using-requestcontext

Vous pouvez alimenter RequestContext dans un middleware du serveur à l'exécution en extrayant des informations de la requête. Dans cet exemple, temperature-unit est défini d'après l'en-tête Cloudflare CF-IPCountry afin que les réponses correspondent aux paramètres régionaux de l'utilisateur.

src/mastra/index.ts
import { Mastra } from '@mastra/core'
import { RequestContext } from '@mastra/core/request-context'
import { testWeatherAgent } from './agents/test-weather-agent'

export const mastra = new Mastra({
agents: { testWeatherAgent },
server: {
middleware: [
async (context, next) => {
const country = context.req.header('CF-IPCountry')
const requestContext = context.get('requestContext')

requestContext.set('temperature-unit', country === 'US' ? 'fahrenheit' : 'celsius')

await next()
},
],
},
})

Authentication
Lien direct vers Authentication

{
handler: async (c, next) => {
const authHeader = c.req.header('Authorization');
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return new Response('Unauthorized', { status: 401 });
}

// Validate token here
await next();
},
path: '/api/*',
}

Autorisation (isolation des utilisateurs)
Lien direct vers Autorisation (isolation des utilisateurs)

L'authentification vérifie l'identité de l'utilisateur. L'autorisation détermine les ressources auxquelles il peut accéder. Sans délimitation par identifiant de ressource, un utilisateur authentifié pourrait accéder aux threads d'autres utilisateurs en devinant leurs identifiants ou en manipulant le paramètre resourceId.

Le moyen le plus simple de limiter la Memory et les threads à l'utilisateur authentifié consiste à utiliser le callback mapUserToResourceId dans la configuration d'authentification :

import { Mastra } from '@mastra/core'

export const mastra = new Mastra({
server: {
auth: {
authenticateToken: async token => {
return verifyToken(token) // { id: 'user-123', orgId: 'org-456', ... }
},
mapUserToResourceId: user => user.id,
},
},
})

Après une authentification réussie, mapUserToResourceId est appelé avec l'objet de l'utilisateur authentifié. La valeur renvoyée est définie comme MASTRA_RESOURCE_ID_KEY dans le contexte de requête. Ce mécanisme fonctionne avec tous les adaptateurs de serveur, notamment Hono, Express et Next.js.

L'identifiant de ressource ne doit pas nécessairement être user.id. Voici quelques modèles courants :

// Org-scoped
mapUserToResourceId: user => `${user.orgId}:${user.id}`

// From a JWT claim
mapUserToResourceId: user => user.tenantId

// Composite key
mapUserToResourceId: user => `${user.workspaceId}:${user.projectId}:${user.id}`

Lorsqu'un identifiant de ressource est défini, le serveur effectue automatiquement les opérations suivantes :

  • Filtrage de la liste des threads afin de ne renvoyer que ceux qui appartiennent à l'utilisateur
  • Validation de l'accès aux threads avec une réponse 403 en cas d'accès au thread d'un autre utilisateur
  • Imposition de l'identifiant de l'utilisateur authentifié lors de la création des threads
  • Validation des opérations sur les messages, y compris la suppression, afin de garantir que les messages appartiennent à des threads détenus par l'utilisateur

Même si un client transmet ?resourceId=other-user-id, la valeur définie par l'authentification prévaut. Toute tentative d'accès à des threads ou messages appartenant à d'autres utilisateurs renvoie une erreur 403.

Avancé : définir l'identifiant de ressource dans un middleware
Lien direct vers Avancé : définir l'identifiant de ressource dans un middleware

Dans les scénarios plus complexes, par exemple lorsque vous devez rechercher l'identifiant de ressource dans une base de données, vous pouvez définir MASTRA_RESOURCE_ID_KEY directement dans un middleware :

import { Mastra } from '@mastra/core'
import { MASTRA_RESOURCE_ID_KEY } from '@mastra/core/request-context'
import { getAuthenticatedUser } from '@mastra/server/auth'

export const mastra = new Mastra({
server: {
auth: {
authenticateToken: async token => verifyToken(token),
},
middleware: [
{
path: '/api/*',
handler: async (c, next) => {
const token = c.req.header('Authorization')
if (!token) {
return c.json({ error: 'Unauthorized' }, 401)
}

const user = await getAuthenticatedUser<{ id: string }>({
mastra: c.get('mastra'),
token,
request: c.req.raw,
})
const requestContext = c.get('requestContext')

if (!user) {
return c.json({ error: 'Unauthorized' }, 401)
}

requestContext.set(MASTRA_RESOURCE_ID_KEY, user.id)
return next()
},
},
],
},
})

server.middleware s'exécute avant les contrôles d'authentification par route de Mastra. Lorsqu'un middleware a besoin de l'utilisateur authentifié, appelez getAuthenticatedUser() afin de l'obtenir depuis le Provider d'authentification configuré sans modifier le flux d'authentification par défaut des routes.

Utiliser MASTRA_THREAD_ID_KEY
Lien direct vers using-mastra_thread_id_key

Vous pouvez également définir MASTRA_THREAD_ID_KEY pour remplacer l'identifiant de thread fourni par le client :

import { MASTRA_RESOURCE_ID_KEY, MASTRA_THREAD_ID_KEY } from '@mastra/core/request-context'

// Force operations to use a specific thread
requestContext.set(MASTRA_THREAD_ID_KEY, validatedThreadId)

Cette approche est utile pour limiter les opérations à un thread précis que vous avez validé par un autre moyen.

Prise en charge de CORS
Lien direct vers Prise en charge de CORS

{
handler: async (c, next) => {
c.header('Access-Control-Allow-Origin', '*');
c.header(
'Access-Control-Allow-Methods',
'GET, POST, PUT, DELETE, OPTIONS',
);
c.header(
'Access-Control-Allow-Headers',
'Content-Type, Authorization',
);

if (c.req.method === 'OPTIONS') {
return new Response(null, { status: 204 });
}

await next();
},
}

Journalisation des requêtes
Lien direct vers Journalisation des requêtes

{
handler: async (c, next) => {
const start = Date.now();
await next();
const duration = Date.now() - start;
console.log(`${c.req.method} ${c.req.url} - ${duration}ms`);
},
}

Voir aussi