メインコンテンツへ移動

カスタム認証 Provider

カスタム認証 Provider を使用すると、組み込み Provider で対応していないアイデンティティシステム向けの認証を実装できます。MastraAuthProvider 基底クラスを拡張することで、任意の認証システムと統合できます。

概要
概要への直接リンク

認証 Provider は、受信リクエストの認証と認可を処理します。

  • トークンの検証とユーザーの抽出
  • ユーザー認可ロジック
  • パスに基づくアクセス制御(公開ルートと保護されたルート)

カスタム認証 Provider は、次の用途に使用できます。

  • セルフホスト型のアイデンティティシステム
  • カスタムトークン形式または検証ロジック
  • 特別な認可ルール
  • エンタープライズ SSO 連携

カスタム認証 Provider の作成
カスタム認証 Provider の作成への直接リンク

MastraAuthProvider クラスを拡張し、必須メソッドを実装します。

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
}
}

必須メソッド
必須メソッドへの直接リンク

authenticateToken()
authenticatetokenへの直接リンク

受信したトークンを検証し、有効な場合はユーザーオブジェクトを、認証に失敗した場合は null を返します。

async authenticateToken(token: string, request: HonoRequest): Promise<TUser | null>
パラメーター説明
tokenstringAuthorization ヘッダーから抽出された bearer token
requestHonoRequest受信リクエストオブジェクト(ヘッダーや Cookie などにアクセス可能)

戻り値: 認証に成功した場合はユーザーオブジェクト、失敗した場合は null

トークンは Authorization: Bearer <token> ヘッダーから自動的に抽出されます。その他のヘッダーや Cookie にアクセスする必要がある場合は、request パラメーターを使用します。

authorizeUser()
authorizeuserへの直接リンク

認証済みユーザーにリソースへのアクセスを許可するかどうかを判定します。

async authorizeUser(user: TUser, request: HonoRequest): Promise<boolean> | boolean
パラメーター説明
userTUserauthenticateToken が返したユーザーオブジェクト
requestHonoRequest受信リクエストオブジェクト

戻り値: アクセスを許可する場合は true、拒否する場合は false(403 Forbidden を返す)。

設定オプション
設定オプションへの直接リンク

MastraAuthProviderOptions インターフェースでは、次のオプションを使用できます。

オプション説明
namestringログ記録やデバッグに使用する Provider 名
authorizeUser(user, request) => Promise<boolean> | booleanカスタム認可関数
protected(RegExp | string | [string, Methods | Methods[]])[]認証が必要なパス
public(RegExp | string | [string, Methods | Methods[]])[]認証を省略するパス

パスパターン
パスパターンへの直接リンク

パターンマッチングを使用して、認証が必要なパスを設定します。

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
],
})

認証 Provider の使用
認証 Provider の使用への直接リンク

カスタム認証 Provider を 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,
}),
},
})

ヘルパーユーティリティ
ヘルパーユーティリティへの直接リンク

@mastra/auth パッケージは、一般的なトークン検証パターンに使用できるユーティリティを提供します。

JWT の検証
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)

例: JWKS ベースの Provider
例: JWKS ベースの 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
}
}

カスタム認可ロジック
カスタム認可ロジックへの直接リンク

カスタム authorizeUser 関数を指定して、デフォルトの認可を上書きします。

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')
},
})

ロールベースの認可
ロールベースの認可への直接リンク

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
},
})

カスタム認証 Provider のテスト
カスタム認証 Provider のテストへの直接リンク

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)
})
})
})

エラー処理
エラー処理への直接リンク

よくある失敗シナリオには、状況が分かるエラーメッセージを用意します。

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
}
}
}

組み込み Provider
組み込み Providerへの直接リンク

Mastra には、リファレンス実装として次の認証 Provider が含まれています。

  • MastraJwtAuth: HMAC シークレットを使用したシンプルな JWT 検証(@mastra/auth
  • MastraAuthClerk: Clerk 認証(@mastra/auth-clerk
  • MastraAuthAuth0: Auth0 認証(@mastra/auth-auth0
  • MastraAuthSupabase: Supabase 認証(@mastra/auth-supabase
  • MastraAuthFirebase: Firebase 認証(@mastra/auth-firebase
  • MastraAuthWorkOS: WorkOS 認証(@mastra/auth-workos
  • MastraAuthBetterAuth: Better Auth 連携(@mastra/auth-better-auth
  • SimpleAuth: 開発用のトークンとユーザーのマッピング(@mastra/core/server

実装の詳細については、ソースコードを参照してください。