> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentfront.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Remote OAuth Providers

> Connect to external identity providers like Auth0, Okta, and Azure AD

FrontMCP supports two approaches for integrating external identity providers:

<CardGroup cols={2}>
  <Card title="Transparent Mode" icon="arrow-right-arrow-left">
    Pass-through tokens from the IdP. FrontMCP validates but doesn't issue tokens.

    **Best for:** Single IdP, existing auth infrastructure
  </Card>

  <Card title="Orchestrated Remote" icon="sitemap">
    FrontMCP acts as OAuth server, proxying user authentication to upstream IdP.

    **Best for:** Multi-provider, progressive auth, federated scenarios
  </Card>
</CardGroup>

***

## Transparent Mode

Direct token pass-through from external identity provider.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@FrontMcp({
  info: { name: 'MyServer', version: '1.0.0' },
  auth: {
    mode: 'transparent',
    provider: 'https://auth.example.com',
    expectedAudience: 'https://api.myservice.com',
  },
})
export class Server {}
```

### Configuration Options

| Option | Type | Default | Description |
| - | - | - | - |
| `provider` | `string` | Required | Base URL of the identity provider |
| `providerConfig.jwksUri` | `string` | Auto-discovered | Custom JWKS endpoint |
| `providerConfig.jwks` | `JSONWebKeySet` | - | Inline JWKS for offline verification |
| `expectedAudience` | `string \| string[]` | Resource URL | Required audience claim value(s) |
| `requiredScopes` | `string[]` | `[]` | Scopes that must be present in token |
| `allowAnonymous` | `boolean` | `false` | Allow requests without tokens |

### Provider Examples

<Tabs>
  <Tab title="Auth0">
    ```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    @FrontMcp({
      info: { name: 'MyServer', version: '1.0.0' },
      auth: {
        mode: 'transparent',
        provider: 'https://your-tenant.auth0.com',
        // JWKS auto-discovered from /.well-known/jwks.json
        expectedAudience: 'https://api.yourservice.com',
        requiredScopes: ['openid', 'profile'],
      },
    })
    export class Server {}
    ```
  </Tab>

  <Tab title="Okta">
    ```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    @FrontMcp({
      info: { name: 'MyServer', version: '1.0.0' },
      auth: {
        mode: 'transparent',
        provider: 'https://your-org.okta.com/oauth2/default',
        expectedAudience: 'api://default',
      },
    })
    export class Server {}
    ```
  </Tab>

  <Tab title="Azure AD">
    ```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    @FrontMcp({
      info: { name: 'MyServer', version: '1.0.0' },
      auth: {
        mode: 'transparent',
        provider: 'https://login.microsoftonline.com/{tenant}/v2.0',
        providerConfig: {
          jwksUri: 'https://login.microsoftonline.com/{tenant}/discovery/v2.0/keys',
        },
        expectedAudience: 'api://{client-id}',
      },
    })
    export class Server {}
    ```
  </Tab>

  <Tab title="Google">
    ```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    @FrontMcp({
      info: { name: 'MyServer', version: '1.0.0' },
      auth: {
        mode: 'transparent',
        provider: 'https://accounts.google.com',
        providerConfig: {
          jwksUri: 'https://www.googleapis.com/oauth2/v3/certs',
        },
        expectedAudience: '{your-client-id}.apps.googleusercontent.com',
      },
    })
    export class Server {}
    ```
  </Tab>
</Tabs>

### Token Flow

```mermaid theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
sequenceDiagram
    participant Client
    participant FrontMCP
    participant IdP as Identity Provider

    Client->>IdP: Authenticate (OAuth flow)
    IdP-->>Client: Access Token (JWT)

    Client->>FrontMCP: API Request + Bearer token
    FrontMCP->>IdP: Fetch JWKS (cached)
    FrontMCP->>FrontMCP: Verify JWT signature
    FrontMCP->>FrontMCP: Validate claims (aud, exp, iss)
    FrontMCP-->>Client: Authorized response
```

***

## Orchestrated Remote Mode

FrontMCP acts as an OAuth server while proxying user authentication to a single
**mandatory** upstream IdP.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@FrontMcp({
  info: { name: 'MyServer', version: '1.0.0' },
  auth: {
    mode: 'remote',
    provider: 'https://auth.example.com',
    clientId: 'your-client-id',
    clientSecret: 'your-client-secret',
    scopes: ['openid', 'profile', 'email'],
    consent: { enabled: true },
  },
})
export class Server {}
```

<Info>
  In `mode: 'remote'`, `GET /oauth/authorize` redirects **straight to the upstream
  IdP** — FrontMCP never shows its own login page or a provider-selection page.
  After the IdP returns to `/oauth/provider/{id}/callback`, FrontMCP exchanges the
  code, stores the upstream tokens (encrypted, server-side), derives the session
  identity (`sub`/`email`/`name`) from the **upstream user**, and mints its own
  HS256-signed session token. Tools read the upstream token with
  `this.orchestration.getToken('<provider-id>')` — the provider id is taken from
  `providerConfig.id` (or derived from the `provider` hostname).
</Info>

### Configuration Options

| Option | Type | Default | Description |
| - | - | - | - |
| `provider` | `string` | Required | Upstream IdP base URL |
| `clientId` | `string` | Required | OAuth client ID |
| `clientSecret` | `string` | - | OAuth client secret (confidential clients) |
| `scopes` | `string[]` | `['openid']` | Scopes to request from IdP |
| `providerConfig.dcrEnabled` | `boolean` | `false` | Use Dynamic Client Registration |
| `consent` | `ConsentConfig` | `{ enabled: false }` | Show consent UI after IdP login |
| `requireRegisteredClients` | `boolean` | `true` | Refuse an MCP client that isn't registered |
| `allowedScopes` | `string[]` | OpenID scopes | Scopes FrontMCP grants in its own tokens |
| `expectedAudience` | `string \| string[]` | Resource URL | Audiences (`aud`) whose tokens this server accepts, in place of the request's URL |

<Note>
  `/oauth/authorize` gives the browser a `frontmcp_signin_<id>` cookie (`__Host-frontmcp_signin_<id>` over https), and the
  provider callback accepts the sign-in only from the browser that holds it, so
  the browser must start the sign-in on the host the IdP returns to (the
  issuer's host). See [Sign-in cookie](/frontmcp/authentication/local#sign-in-cookie).
</Note>

### MCP client registration

`requireRegisteredClients` defaults to `true`: `/oauth/authorize` answers a
`client_id` this server doesn't know with a 400 page (`Unknown client_id`)
instead of issuing a code to whatever `redirect_uri` the request names. A client
is known when it registered through FrontMCP's `/oauth/register` (DCR) or
identifies with a [CIMD](/frontmcp/authentication/cimd) client-id URL.

Remote mode has no `dcr` block, so there is no list of pre-registered clients,
and `/oauth/register` is on only outside production (`NODE_ENV !== 'production'`,
registrations kept in memory). A production remote server with the defaults
therefore serves **CIMD clients only**.

<Warning>
  **Upgrading a remote-mode server from 1.8.2 or earlier.** There
  `requireRegisteredClients` defaulted to `false`, so MCP clients that sent a
  plain `client_id` without registering were accepted. They now get `Unknown
      client_id`. To keep them working:

  * have them use a CIMD client-id URL (an HTTPS URL of their client metadata
    document; FrontMCP fetches it and checks `redirect_uri` against it), or
  * set `requireRegisteredClients: false` for local development only. An
    unregistered client's `redirect_uri` can't be checked against anything, so
    any site could receive a sign-in code for a `client_id` it names.
</Warning>

### When to Use Orchestrated Remote

<Check>**Multiple identity providers** - Federate users from different IdPs under one session</Check>
<Check>**Progressive authorization** - Users authorize apps incrementally</Check>
<Check>**Custom token claims** - Add claims not available from upstream</Check>
<Check>**Consent UI** - Let users select which tools/resources to grant</Check>

### Token Flow

```mermaid theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
sequenceDiagram
    participant User
    participant Client
    participant FrontMCP
    participant IdP as Upstream IdP

    Client->>FrontMCP: GET /oauth/authorize
    FrontMCP-->>User: Redirect to IdP

    User->>IdP: Login
    IdP-->>FrontMCP: Authorization code

    FrontMCP->>IdP: Exchange code for tokens
    IdP-->>FrontMCP: IdP tokens

    FrontMCP->>FrontMCP: Create session
    FrontMCP->>FrontMCP: Sign FrontMCP token
    FrontMCP-->>Client: Redirect with code

    Client->>FrontMCP: Exchange code
    FrontMCP-->>Client: FrontMCP tokens
```

***

## Dynamic Client Registration

<Warning>
  **Not yet wired.** `providerConfig.dcrEnabled` / `providerConfig.registrationEndpoint`
  are accepted by the schema but FrontMCP does not yet perform upstream Dynamic
  Client Registration. In `mode: 'remote'` you must provide a **pre-registered**
  `clientId` (and `clientSecret` for confidential clients) — without a `clientId`
  the upstream provider is not registered and `/oauth/authorize` returns a
  configuration error. DCR support is planned; track it before relying on it.
</Warning>

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
auth: {
  mode: 'remote',
  provider: 'https://auth.example.com',
  clientId: 'pre-registered-client-id', // required today (DCR not yet wired)
  clientSecret: process.env.IDP_CLIENT_SECRET,
}
```

***

## Endpoint Overrides

Override auto-discovered endpoints for non-standard IdPs:

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
auth: {
  mode: 'remote',
  provider: 'https://legacy-idp.example.com',
  providerConfig: {
    authEndpoint: 'https://legacy-idp.example.com/auth',
    tokenEndpoint: 'https://legacy-idp.example.com/token',
    userInfoEndpoint: 'https://legacy-idp.example.com/userinfo',
    jwksUri: 'https://legacy-idp.example.com/keys',
  },
}
```

***

## Inline JWKS

For offline verification or non-discoverable providers:

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
auth: {
  mode: 'transparent',
  provider: 'https://auth.example.com',
  providerConfig: {
    jwks: {
      keys: [
        {
          kty: 'RSA',
          kid: 'key-id-1',
          alg: 'RS256',
          n: '...',
          e: 'AQAB',
        },
      ],
    },
  },
}
```

***

## Multi-Provider Setup

Combine multiple providers with orchestrated mode:

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@App({
  name: 'Slack',
  auth: {
    mode: 'transparent',
    provider: 'https://slack.com/oauth',
  },
  standalone: true,
})
export class SlackApp {}

@App({
  name: 'CRM',
  auth: {
    mode: 'transparent',
    provider: 'https://company.auth0.com',
  },
})
export class CrmApp {}

@FrontMcp({
  info: { name: 'Suite', version: '1.0.0' },
  apps: [SlackApp, CrmApp],
  auth: {
    mode: 'local',
    consent: { enabled: true },
  },
})
export class Server {}
```

***

## Per-App Remote Auth

Configure different providers per app:

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@App({
  name: 'Billing',
  auth: {
    mode: 'transparent',
    provider: 'https://billing.auth0.com',
  },
})
export class BillingApp {}

@App({
  name: 'Analytics',
  auth: {
    mode: 'transparent',
    provider: 'https://analytics.okta.com/oauth2/default',
  },
})
export class AnalyticsApp {}

@FrontMcp({
  info: { name: 'Suite', version: '1.0.0' },
  apps: [BillingApp, AnalyticsApp],
  splitByApp: true,
})
export class Server {}
```

<Tip>
  Use `standalone: true` to expose an app's OAuth endpoints directly, bypassing the parent auth.
</Tip>

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="Token verification fails">
    * Check `expectedAudience` matches the token's `aud` claim
    * Verify JWKS endpoint is accessible
    * Ensure token hasn't expired
  </Accordion>

  <Accordion title="JWKS fetch fails">
    * Verify IdP URL is correct
    * Check network connectivity
    * Try providing inline JWKS via `providerConfig.jwks`
  </Accordion>

  <Accordion title="Redirect URI mismatch">
    * Register exact redirect URI with IdP
    * Check for trailing slashes
    * Ensure protocol (http/https) matches
  </Accordion>
</AccordionGroup>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Remote Proxy" icon="share" href="/frontmcp/authentication/remote-proxy">
    Handle IdPs without DCR support
  </Card>

  <Card title="Progressive Authorization" icon="forward" href="/frontmcp/authentication/progressive">
    Implement incremental app authorization
  </Card>

  <Card title="Local OAuth" icon="server" href="/frontmcp/authentication/local">
    Built-in OAuth server setup
  </Card>

  <Card title="Production Checklist" icon="clipboard-check" href="/frontmcp/authentication/production">
    Security requirements for deployment
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.