CompositeAuth Class
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, no additional packages required.
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
The order of providers matters. Place the most common authentication method first for better 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
CompositeAuth silently catches errors from individual providers and moves to the next one. This prevents one failing provider from blocking authentication:
// If clerkAuth throws an error, apiKeyAuth still gets tried
new CompositeAuth([clerkAuth, apiKeyAuth]);
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 from the
Authorizationheader - 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);
}
}
RelatedDirect link to Related
- Auth Overview - Authentication concepts
- Simple Auth - Token-to-user mapping
- Custom Auth Provider - Build your own provider