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

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

```typescript
import { CompositeAuth } from '@mastra/core/server'
```

## Usage example

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

```typescript
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

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`.

```typescript
// 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

The order of providers matters. Place the most common authentication method first for better performance:

```typescript
// 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

Support users from different identity providers:

```typescript
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

Migrate from JWT to Clerk while maintaining backwards compatibility:

```typescript
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

Combine built-in providers with custom implementations:

```typescript
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

CompositeAuth silently catches errors from individual providers and moves to the next one. This prevents one failing provider from blocking authentication:

```typescript
// 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

- 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

When providers return different user types, use a discriminated union:

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