Custom Auth Provider in Mastra
Trust: ★★★☆☆ (0.90) · 0 validations · developer_reference
Published: 2026-05-10 · Source: crawler_authoritative
Tình huống
Guide for developers implementing custom authentication providers in Mastra by extending the MastraAuthProvider base class to support self-hosted identity systems, custom token formats, or enterprise SSO integrations.
Insight
The MastraAuthProvider base class requires implementing two core methods: authenticateToken() and authorizeUser(). The authenticateToken() method receives the bearer token from the Authorization header and the HonoRequest object, returning the user object or null on failure. The authorizeUser() method receives the authenticated user and request, returning true to allow access or false to deny (403 Forbidden). Custom providers extend MastraAuthProvider
Hành động
To create a custom auth provider: 1) Extend MastraAuthProvider
Kết quả
The custom auth provider handles token verification, user extraction, and authorization for incoming requests. Protected paths return 401 for missing tokens and 403 for unauthorized users. Public paths bypass authentication. The provider name appears in logs for debugging.
Điều kiện áp dụng
Requires @mastra/core/server for MastraAuthProvider base class and @mastra/auth for helper utilities. Generic type parameter should match your user object structure.
Nội dung gốc (Original)
Custom auth provider
Custom auth providers allow you to implement authentication for identity systems that aren’t covered by the built-in providers. Extend the MastraAuthProvider base class to integrate with any authentication system.
Overview
Auth providers handle authentication and authorization for incoming requests:
- Token verification and user extraction
- User authorization logic
- Path-based access control (public/protected routes)
Create custom auth providers to support:
- Self-hosted identity systems
- Custom token formats or verification logic
- Specialized authorization rules
- Enterprise SSO integrations
Creating a custom auth provider
Extend the MastraAuthProvider class and implement the required methods:
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
}
}Required methods
authenticateToken()
Verify the incoming token and return the user object if valid, or null if authentication fails.
async authenticateToken(token: string, request: HonoRequest): Promise<TUser | null>| Parameter | Type | Description |
|---|---|---|
token | string | The bearer token extracted from the Authorization header |
request | HonoRequest | The incoming request object (access headers, cookies, etc.) |
Returns: The user object if authentication succeeds, or null if it fails.
The token is automatically extracted from the Authorization: Bearer <token> header. If you need to access other headers or cookies, use the request parameter.
authorizeUser()
Determine if the authenticated user is allowed to access the resource.
async authorizeUser(user: TUser, request: HonoRequest): Promise<boolean> | boolean| Parameter | Type | Description |
|---|---|---|
user | TUser | The user object returned by authenticateToken |
request | HonoRequest | The incoming request object |
Returns: true to allow access, false to deny (returns 403 Forbidden).
Configuration options
The MastraAuthProviderOptions interface supports these options:
| Option | Type | Description |
|---|---|---|
name | string | Provider name for logging/debugging |
authorizeUser | (user, request) => Promise<boolean> | boolean | Custom authorization function |
protected | (RegExp | string | [string, Methods | Methods[]])[] | Paths that require authentication |
public | (RegExp | string | [string, Methods | Methods[]])[] | Paths that bypass authentication |
Path Patterns
Configure which paths require authentication using pattern matching:
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
],
})Using your auth provider
Register your custom auth provider with the Mastra instance:
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,
}),
},
})Helper utilities
The @mastra/auth package provides utilities for common token verification patterns:
JWT Verification
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)Example: JWKS-based Provider
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
}
}Custom authorization logic
Override the default authorization by providing a custom authorizeUser function:
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')
},
})Role-based Authorization
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
},
})Testing custom auth providers
Example test structure using 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', roles: ['admin'] }
const regularUser = { id: 'user456', email: '[email protected]', 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 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
}
}
}Built-in providers
Mastra includes these auth providers as reference implementations:
- MastraJwtAuth: Simple JWT verification with HMAC secrets (
@mastra/auth) - MastraAuthClerk: Clerk authentication (
@mastra/auth-clerk) - MastraAuthAuth0: Auth0 authentication (
@mastra/auth-auth0) - MastraAuthSupabase: Supabase authentication (
@mastra/auth-supabase) - MastraAuthFirebase: Firebase authentication (
@mastra/auth-firebase) - MastraAuthWorkOS: WorkOS authentication (
@mastra/auth-workos) - MastraAuthBetterAuth: Better Auth integration (
@mastra/auth-better-auth) - SimpleAuth: Token-to-user mapping for development (
@mastra/core/server)
See the source code for implementation details.
Related
- Auth Overview: Authentication concepts and configuration
- Custom API Routes: Controlling authentication on custom endpoints
Liên kết
- Nền tảng: Dev Framework · Mastra
- Nguồn: https://mastra.ai/docs/server/auth/custom-auth-provider
Xem thêm: