Composite Auth
The CompositeAuth class allows you to combine multiple authentication providers into a single auth handler. It tries each provider in order until one succeeds.
Use casesDirect link to Use cases
- Support both API keys and OAuth tokens
- Migrate between auth providers without breaking existing clients
- Allow multiple identity providers (e.g., Clerk for web, API keys for integrations)
- Gradual rollout of new authentication methods
InstallationDirect link to Installation
CompositeAuth is included in @mastra/core and requires no additional packages.
import { CompositeAuth } from '@mastra/core/server'
Usage exampleDirect link to Usage example
Combine SimpleAuth (for API keys) with Clerk (for user sessions):
import { Mastra } from '@mastra/core'
import { CompositeAuth, SimpleAuth } from '@mastra/core/server'
import { MastraAuthClerk } from '@mastra/auth-clerk'
// API key users
type ApiKeyUser = {
id: string
name: string
type: 'api-key'
}
const apiKeyAuth = new SimpleAuth<ApiKeyUser>({
tokens: {
'sk-integration-key-123': {
id: 'integration-1',
name: 'CI/CD Pipeline',
type: 'api-key',
},
},
})
// Clerk users (from web app)
const clerkAuth = new MastraAuthClerk({
publishableKey: process.env.CLERK_PUBLISHABLE_KEY,
secretKey: process.env.CLERK_SECRET_KEY,
jwksUri: process.env.CLERK_JWKS_URI,
})
export const mastra = new Mastra({
server: {
auth: new CompositeAuth([apiKeyAuth, clerkAuth]),
},
})
How it worksDirect link to How it works
When a request comes in, CompositeAuth:
- Extracts the token from the
Authorizationheader - Tries each provider's
authenticateToken()method in order - Returns the user from the first provider that succeeds
- Returns
null(401 Unauthorized) if all providers fail
For authorization, it calls each provider's authorizeUser() method until one returns true.
// Pseudocode of CompositeAuth behavior
async authenticateToken(token, request) {
for (const provider of this.providers) {
const user = await provider.authenticateToken(token, request);
if (user) return user; // First match wins
}
return null; // All providers failed
}
Provider orderDirect link to Provider order
Place the most common authentication method first because provider order affects performance:
// If most requests use Clerk, put it first
new CompositeAuth([
clerkAuth, // Checked first (most common)
apiKeyAuth, // Checked second (less common)
])
// If most requests use API keys, put it first
new CompositeAuth([
apiKeyAuth, // Checked first (most common)
clerkAuth, // Checked second (less common)
])
Multiple OAuth providersDirect link to Multiple OAuth providers
Support users from different identity providers:
import { CompositeAuth } from '@mastra/core/server'
import { MastraAuthClerk } from '@mastra/auth-clerk'
import { MastraAuthAuth0 } from '@mastra/auth-auth0'
const clerkAuth = new MastraAuthClerk({
publishableKey: process.env.CLERK_PUBLISHABLE_KEY,
secretKey: process.env.CLERK_SECRET_KEY,
jwksUri: process.env.CLERK_JWKS_URI,
})
const auth0Auth = new MastraAuthAuth0({
domain: process.env.AUTH0_DOMAIN,
audience: process.env.AUTH0_AUDIENCE,
})
export const mastra = new Mastra({
server: {
auth: new CompositeAuth([clerkAuth, auth0Auth]),
},
})
Migration exampleDirect link to Migration example
Migrate from JWT to Clerk while maintaining backwards compatibility:
import { CompositeAuth } from '@mastra/core/server'
import { MastraJwtAuth } from '@mastra/auth'
import { MastraAuthClerk } from '@mastra/auth-clerk'
// Legacy JWT auth (existing clients)
const legacyAuth = new MastraJwtAuth({
secret: process.env.JWT_SECRET,
})
// New Clerk auth (new clients)
const clerkAuth = new MastraAuthClerk({
publishableKey: process.env.CLERK_PUBLISHABLE_KEY,
secretKey: process.env.CLERK_SECRET_KEY,
jwksUri: process.env.CLERK_JWKS_URI,
})
// Support both during migration
export const mastra = new Mastra({
server: {
auth: new CompositeAuth([
clerkAuth, // New auth method (preferred)
legacyAuth, // Legacy support
]),
},
})
With custom providersDirect link to With custom providers
Combine built-in providers with custom implementations:
import { CompositeAuth, SimpleAuth } from '@mastra/core/server'
import { MyCustomAuth } from './my-custom-auth'
const apiKeyAuth = new SimpleAuth({
tokens: {
'sk-key-123': { id: 'user-1', name: 'API User' },
},
})
const customAuth = new MyCustomAuth({
apiUrl: process.env.CUSTOM_AUTH_URL,
})
export const mastra = new Mastra({
server: {
auth: new CompositeAuth([apiKeyAuth, customAuth]),
},
})
Error handlingDirect link to Error handling
During authentication, CompositeAuth silently catches errors thrown by a provider's authenticateToken() method and moves to the next provider. This prevents one failing provider from blocking authentication:
// If clerkAuth throws while authenticating the token, apiKeyAuth still gets tried
new CompositeAuth([clerkAuth, apiKeyAuth])
Authorization doesn't catch errors. CompositeAuth calls each provider's authorizeUser() method in order and grants access as soon as one returns true. If a provider's authorizeUser() throws, CompositeAuth stops checking the remaining providers and the request fails with a 500 error. To deny access or let the next provider decide, return false from authorizeUser() instead of throwing.
To debug authentication issues, add logging to your custom providers or check individual provider configurations.
LimitationsDirect link to Limitations
- All providers share the same token, taken from the
Authorizationheader or, when that header is missing, theapiKeyquery parameter - User types may differ between providers (use discriminated unions if needed)
- No built-in way to identify which provider authenticated a request
Handling Different User TypesDirect link to Handling Different User Types
When providers return different user types, use a discriminated union:
type ApiKeyUser = {
type: 'api-key'
id: string
name: string
}
type ClerkUser = {
type: 'clerk'
sub: string
email: string
}
type User = ApiKeyUser | ClerkUser
// In your application code
function handleUser(user: User) {
if (user.type === 'api-key') {
console.log('API key user:', user.name)
} else {
console.log('Clerk user:', user.email)
}
}