Skip to main content
Mastra 1.0 is available 🎉 Read announcement

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 Cases
Direct 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

Installation
Direct link to Installation

CompositeAuth is included in @mastra/core, no additional packages required.

import { CompositeAuth } from '@mastra/core/server';

Usage Example
Direct link to Usage Example

Combine SimpleAuth (for API keys) with Clerk (for user sessions):

src/mastra/index.ts
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 Works
Direct link to How It Works

When a request comes in, CompositeAuth:

  1. Extracts the token from the Authorization header
  2. Tries each provider's authenticateToken() method in order
  3. Returns the user from the first provider that succeeds
  4. 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 Order
Direct 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 Providers
Direct 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 Example
Direct 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 Providers
Direct 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 Handling
Direct 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.

Limitations
Direct link to Limitations

  • All providers share the same token from the Authorization header
  • 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 Types
Direct 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);
}
}