Provider d'authentification personnalisé
Les Providers d'authentification personnalisés permettent d'implémenter l'authentification de systèmes d'identité non couverts par les Providers intégrés. Étendez la classe de base MastraAuthProvider pour intégrer n'importe quel système d'authentification.
PrésentationLien direct vers Présentation
Les Providers d'authentification gèrent l'authentification et l'autorisation des requêtes entrantes :
- Vérification des jetons et extraction de l'utilisateur
- Logique d'autorisation des utilisateurs
- Contrôle d'accès fondé sur les chemins (routes publiques ou protégées)
Créez des Providers d'authentification personnalisés pour prendre en charge :
- les systèmes d'identité auto-hébergés ;
- les formats de jetons ou les logiques de vérification personnalisés ;
- les règles d'autorisation spécialisées ;
- les intégrations SSO d'entreprise.
Créer un Provider d'authentification personnaliséLien direct vers Créer un Provider d'authentification personnalisé
Étendez la classe MastraAuthProvider et implémentez les méthodes requises :
import { MastraAuthProvider } from '@mastra/core/server'
import type { MastraAuthProviderOptions } from '@mastra/core/server'
import type { HonoRequest } from 'hono'
// Define your user type
type MyUser = {
id: string
email: string
roles: string[]
}
// Define options for your provider
interface MyAuthOptions extends MastraAuthProviderOptions<MyUser> {
apiUrl?: string
apiKey?: string
}
export class MyAuthProvider extends MastraAuthProvider<MyUser> {
protected apiUrl: string
protected apiKey: string
constructor(options?: MyAuthOptions) {
// Call super with a name for logging/debugging
super({ name: options?.name ?? 'my-auth' })
const apiUrl = options?.apiUrl ?? process.env.MY_AUTH_API_URL
const apiKey = options?.apiKey ?? process.env.MY_AUTH_API_KEY
if (!apiUrl || !apiKey) {
throw new Error(
'Auth API URL and API key are required. Provide them in options or set MY_AUTH_API_URL and MY_AUTH_API_KEY environment variables.',
)
}
this.apiUrl = apiUrl
this.apiKey = apiKey
// Register any custom options (authorizeUser override, public/protected paths)
this.registerOptions(options)
}
/**
* Verify the token and return the user
* Return null if authentication fails
*/
async authenticateToken(token: string, request: HonoRequest): Promise<MyUser | null> {
try {
const response = await fetch(`${this.apiUrl}/verify`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': this.apiKey,
},
body: JSON.stringify({ token }),
})
if (!response.ok) {
return null
}
const user = await response.json()
return user
} catch (error) {
console.error('Token verification failed:', error)
return null
}
}
/**
* Check if the authenticated user is authorized
* Return true to allow access, false to deny
*/
async authorizeUser(user: MyUser, request: HonoRequest): Promise<boolean> {
// Basic authorization: user must exist and have an ID
return !!user?.id
}
}
Méthodes requisesLien direct vers Méthodes requises
authenticateToken()Lien direct vers authenticatetoken
Vérifiez le jeton entrant et renvoyez l'objet utilisateur s'il est valide, ou null si l'authentification échoue.
async authenticateToken(token: string, request: HonoRequest): Promise<TUser | null>
| Paramètre | Type | Description |
|---|---|---|
token | string | Jeton bearer extrait de l'en-tête Authorization |
request | HonoRequest | Objet de la requête entrante donnant accès aux en-têtes, cookies, etc. |
Renvoie : l'objet utilisateur si l'authentification réussit, ou null si elle échoue.
Le jeton est automatiquement extrait de l'en-tête Authorization: Bearer <token>. Pour accéder à d'autres en-têtes ou cookies, utilisez le paramètre request.
authorizeUser()Lien direct vers authorizeuser
Déterminez si l'utilisateur authentifié est autorisé à accéder à la ressource.
async authorizeUser(user: TUser, request: HonoRequest): Promise<boolean> | boolean
| Paramètre | Type | Description |
|---|---|---|
user | TUser | Objet utilisateur renvoyé par authenticateToken |
request | HonoRequest | Objet de la requête entrante |
Renvoie : true pour autoriser l'accès, false pour le refuser avec une réponse 403 Forbidden.
Options de configurationLien direct vers Options de configuration
L'interface MastraAuthProviderOptions accepte les options suivantes :
| Option | Type | Description |
|---|---|---|
name | string | Nom du Provider utilisé pour la journalisation et le débogage |
authorizeUser | (user, request) => Promise<boolean> | boolean | Fonction d'autorisation personnalisée |
protected | (RegExp | string | [string, Methods | Methods[]])[] | Chemins nécessitant une authentification |
public | (RegExp | string | [string, Methods | Methods[]])[] | Chemins qui contournent l'authentification |
Motifs de cheminLien direct vers Motifs de chemin
Configurez les chemins qui nécessitent une authentification au moyen de motifs :
const auth = new MyAuthProvider({
// Paths that require authentication
protected: [
'/api/*', // Wildcard: all /api routes
'/admin/*', // Wildcard: all /admin routes
/^\/secure\/.*/, // Regex pattern
],
// Paths that bypass authentication
public: [
'/health', // Exact match
'/api/status', // Exact match
['/api/webhook', 'POST'], // Only POST requests to /api/webhook
],
})
Utiliser votre Provider d'authentificationLien direct vers Utiliser votre Provider d'authentification
Enregistrez votre Provider d'authentification personnalisé auprès de l'instance Mastra :
import { Mastra } from '@mastra/core'
import { MyAuthProvider } from './my-auth-provider'
export const mastra = new Mastra({
server: {
auth: new MyAuthProvider({
apiUrl: process.env.MY_AUTH_API_URL,
apiKey: process.env.MY_AUTH_API_KEY,
}),
},
})
Utilitaires d'assistanceLien direct vers Utilitaires d'assistance
Le package @mastra/auth fournit des utilitaires pour les modèles courants de vérification des jetons :
Vérification JWTLien direct vers Vérification JWT
import { verifyHmac, verifyJwks, decodeToken, getTokenIssuer } from '@mastra/auth'
// Verify HMAC-signed JWT
const payload = await verifyHmac(token, 'your-secret-key')
// Verify with JWKS (for OAuth providers)
const payload = await verifyJwks(token, 'https://provider.com/.well-known/jwks.json')
// Decode without verification (for inspection)
const decoded = await decodeToken(token)
// Get the issuer from a decoded token
const issuer = getTokenIssuer(decoded)
Exemple : Provider fondé sur JWKSLien direct vers Exemple : Provider fondé sur JWKS
import { MastraAuthProvider } from '@mastra/core/server'
import type { MastraAuthProviderOptions } from '@mastra/core/server'
import { verifyJwks } from '@mastra/auth'
import type { JwtPayload } from '@mastra/auth'
type MyUser = JwtPayload
interface MyJwksAuthOptions extends MastraAuthProviderOptions<MyUser> {
jwksUri?: string
issuer?: string
}
export class MyJwksAuth extends MastraAuthProvider<MyUser> {
protected jwksUri: string
protected issuer: string
constructor(options?: MyJwksAuthOptions) {
super({ name: options?.name ?? 'my-jwks-auth' })
const jwksUri = options?.jwksUri ?? process.env.MY_JWKS_URI
const issuer = options?.issuer ?? process.env.MY_AUTH_ISSUER
if (!jwksUri) {
throw new Error('JWKS URI is required')
}
this.jwksUri = jwksUri
this.issuer = issuer ?? ''
this.registerOptions(options)
}
async authenticateToken(token: string): Promise<MyUser | null> {
try {
const payload = await verifyJwks(token, this.jwksUri)
// Optionally validate issuer
if (this.issuer && payload.iss !== this.issuer) {
return null
}
return payload
} catch {
return null
}
}
async authorizeUser(user: MyUser): Promise<boolean> {
// Check token hasn't expired
if (user.exp && user.exp * 1000 < Date.now()) {
return false
}
return !!user.sub
}
}
Logique d'autorisation personnaliséeLien direct vers Logique d'autorisation personnalisée
Remplacez l'autorisation par défaut en fournissant une fonction authorizeUser personnalisée :
const auth = new MyAuthProvider({
apiUrl: process.env.MY_AUTH_API_URL,
apiKey: process.env.MY_AUTH_API_KEY,
// Custom authorization: require admin role for all requests
async authorizeUser(user, request) {
return user.roles.includes('admin')
},
})
Autorisation fondée sur les rôlesLien direct vers Autorisation fondée sur les rôles
const auth = new MyAuthProvider({
async authorizeUser(user, request) {
const path = request.url
const method = request.method
// Admin routes require admin role
if (path.startsWith('/admin/')) {
return user.roles.includes('admin')
}
// Write operations require write role
if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) {
return user.roles.includes('write') || user.roles.includes('admin')
}
// Read operations allowed for all authenticated users
return true
},
})
Tester les Providers d'authentification personnalisésLien direct vers Tester les Providers d'authentification personnalisés
Exemple de structure de test avec Vitest :
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { MyAuthProvider } from './my-auth-provider'
// Mock fetch for API calls
global.fetch = vi.fn()
describe('MyAuthProvider', () => {
const mockOptions = {
apiUrl: 'https://auth.example.com',
apiKey: 'test-api-key',
}
beforeEach(() => {
vi.clearAllMocks()
})
describe('initialization', () => {
it('should initialize with provided options', () => {
const auth = new MyAuthProvider(mockOptions)
expect(auth).toBeInstanceOf(MyAuthProvider)
})
it('should throw error when required options are missing', () => {
expect(() => new MyAuthProvider({})).toThrow('Auth API URL and API key are required')
})
})
describe('authenticateToken', () => {
it('should return user when token is valid', async () => {
const mockUser = { id: 'user123', email: 'test@example.com', roles: ['read'] }
;(fetch as any).mockResolvedValue({
ok: true,
json: () => Promise.resolve(mockUser),
})
const auth = new MyAuthProvider(mockOptions)
const result = await auth.authenticateToken('valid-token', {} as any)
expect(fetch).toHaveBeenCalledWith(
'https://auth.example.com/verify',
expect.objectContaining({
method: 'POST',
body: JSON.stringify({ token: 'valid-token' }),
}),
)
expect(result).toEqual(mockUser)
})
it('should return null when token is invalid', async () => {
;(fetch as any).mockResolvedValue({ ok: false })
const auth = new MyAuthProvider(mockOptions)
const result = await auth.authenticateToken('invalid-token', {} as any)
expect(result).toBeNull()
})
})
describe('authorizeUser', () => {
it('should return true when user has valid id', async () => {
const auth = new MyAuthProvider(mockOptions)
const result = await auth.authorizeUser(
{ id: 'user123', email: 'test@example.com', roles: [] },
{} as any,
)
expect(result).toBe(true)
})
it('should return false when user has no id', async () => {
const auth = new MyAuthProvider(mockOptions)
const result = await auth.authorizeUser(
{ id: '', email: 'test@example.com', roles: [] },
{} as any,
)
expect(result).toBe(false)
})
})
describe('custom authorization', () => {
it('should use custom authorizeUser when provided', async () => {
const auth = new MyAuthProvider({
...mockOptions,
authorizeUser: user => user.roles.includes('admin'),
})
const adminUser = { id: 'user123', email: 'admin@example.com', roles: ['admin'] }
const regularUser = { id: 'user456', email: 'user@example.com', roles: ['read'] }
expect(await auth.authorizeUser(adminUser, {} as any)).toBe(true)
expect(await auth.authorizeUser(regularUser, {} as any)).toBe(false)
})
})
describe('route configuration', () => {
it('should store public routes configuration', () => {
const publicRoutes = ['/health', '/api/status']
const auth = new MyAuthProvider({
...mockOptions,
public: publicRoutes,
})
expect(auth.public).toEqual(publicRoutes)
})
it('should store protected routes configuration', () => {
const protectedRoutes = ['/api/*', '/admin/*']
const auth = new MyAuthProvider({
...mockOptions,
protected: protectedRoutes,
})
expect(auth.protected).toEqual(protectedRoutes)
})
})
})
Error handlingLien direct vers Error handling
Provide descriptive errors for common failure scenarios:
export class MyAuthProvider extends MastraAuthProvider<MyUser> {
constructor(options?: MyAuthOptions) {
super({ name: options?.name ?? 'my-auth' })
const apiUrl = options?.apiUrl ?? process.env.MY_AUTH_API_URL
const apiKey = options?.apiKey ?? process.env.MY_AUTH_API_KEY
if (!apiUrl) {
throw new Error(
'Missing MY_AUTH_API_URL. Set the environment variable or pass apiUrl in options.',
)
}
if (!apiKey) {
throw new Error(
'Missing MY_AUTH_API_KEY. Set the environment variable or pass apiKey in options.',
)
}
this.apiUrl = apiUrl
this.apiKey = apiKey
this.registerOptions(options)
}
async authenticateToken(token: string): Promise<MyUser | null> {
if (!token || typeof token !== 'string') {
return null // Immediate safe fail
}
try {
const response = await fetch(`${this.apiUrl}/verify`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': this.apiKey,
},
body: JSON.stringify({ token }),
})
if (!response.ok) {
return null
}
return await response.json()
} catch (error) {
// Log error for debugging, but don't expose details to client
console.error('Auth verification error:', error)
return null
}
}
}
Providers intégrésLien direct vers Providers intégrés
Mastra inclut les Providers d'authentification suivants comme implémentations de référence :
- MastraJwtAuth : vérification JWT simple avec des secrets HMAC (
@mastra/auth) - MastraAuthClerk : authentification Clerk (
@mastra/auth-clerk) - MastraAuthAuth0 : authentification Auth0 (
@mastra/auth-auth0) - MastraAuthSupabase : authentification Supabase (
@mastra/auth-supabase) - MastraAuthFirebase : authentification Firebase (
@mastra/auth-firebase) - MastraAuthWorkOS : authentification WorkOS (
@mastra/auth-workos) - MastraAuthBetterAuth : intégration Better Auth (
@mastra/auth-better-auth) - SimpleAuth : association des jetons aux utilisateurs pour le développement (
@mastra/core/server)
Consultez le code source pour les détails d'implémentation.
Voir aussiLien direct vers Voir aussi
- Présentation de l'authentification : concepts et configuration de l'authentification
- Routes d'API personnalisées : contrôler l'authentification des points de terminaison personnalisés