# Configuring Microsoft Entra ID

The MCP Gateway can use Microsoft Entra ID (formerly Azure AD) as the identity
provider behind its downstream OAuth flow. The `mcp-entra-oauth-inbound` policy
is an Entra-friendly wrapper around the generic `mcp-oauth-inbound` policy:
provide your Entra tenant UUID, a client ID, and a client secret, and the policy
derives the v2 OIDC issuer, JWKS URL, and authorize and token URLs for you.

This guide walks through the Microsoft Entra admin center setup, then wires the
policy into a gateway project. Read the
[authentication overview](./overview.mdx) first for the two-layer OAuth model.

:::caution

This policy is **single-tenant**. The multi-tenant aliases `common`,
`organizations`, and `consumers` are not supported because Entra's issuer claim
is tenant-specific and the gateway enforces an exact issuer match. Use a real
tenant UUID.

:::

## Set up Microsoft Entra

The MCP Gateway acts as an OAuth 2.1 authorization server in front of Entra.
Entra handles browser login; the gateway issues its own access tokens that bind
to MCP routes.

### Register an application

1. In the Microsoft Entra admin center, open **Identity → Applications → App
   registrations** and click **New registration**.
2. Give the application a name (for example, `Zuplo MCP Gateway`).
3. Under **Supported account types**, choose **Accounts in this organizational
   directory only (single tenant)**. The wrapper does not support multi-tenant
   modes.
4. Under **Redirect URI**, choose **Web** and set the value to
   `https://<gateway-host>/oauth/callback`. Click **Register**.
5. On the application's **Overview** page, note the **Application (client) ID**
   and the **Directory (tenant) ID**. Both are UUIDs.

### Add a client secret

1. Open **Certificates & secrets** on the application and click **New client
   secret**.
2. Set a description and an expiration window, then click **Add**.
3. Copy the secret **Value** immediately — Entra only shows it once.

### Add the local development redirect URI

1. Open **Authentication** on the application.
2. Add `http://localhost:9000/oauth/callback` under **Web → Redirect URIs**.
3. Save.

### Optional: restrict access

By default any user in the tenant can sign in. To restrict access to specific
groups, open **Enterprise applications**, find the same application, and use
**Properties → Assignment required** plus **Users and groups** assignments.

## Wire the policy into the gateway

Add the policy to `config/policies.json`:

```json
{
  "name": "entra-managed-oauth",
  "policyType": "mcp-entra-oauth-inbound",
  "handler": {
    "module": "$import(@zuplo/runtime/mcp-gateway)",
    "export": "McpEntraOAuthInboundPolicy",
    "options": {
      "tenantId": "$env(ENTRA_TENANT_ID)",
      "clientId": "$env(ENTRA_CLIENT_ID)",
      "clientSecret": "$env(ENTRA_CLIENT_SECRET)"
    }
  }
}
```

Set the three environment variables in your project's environment configuration.
The secret values belong in the project secret store.

Attach the policy to each MCP route in `config/routes.oas.json` and register the
gateway plugin in `modules/zuplo.runtime.ts` (see
[Configuring Auth0](./configuring-auth0.mdx#wire-the-policy-into-the-gateway)
for the route and plugin patterns — they're identical across all wrappers).

## What the wrapper derives

| Generic field           | Derived value                                                        |
| ----------------------- | -------------------------------------------------------------------- |
| `oidc.issuer`           | `https://login.microsoftonline.com/{tenantId}/v2.0`                  |
| `oidc.jwksUrl`          | `https://login.microsoftonline.com/{tenantId}/discovery/v2.0/keys`   |
| `browserLogin.url`      | `https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/authorize` |
| `browserLogin.tokenUrl` | `https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token`     |

## Test the configuration

The fastest sanity check is to connect an MCP client:

1. Open Claude Desktop, Cursor, Claude Code, or another OAuth-aware MCP client.
2. Add a remote MCP server pointing at one of your `/mcp/{slug}` routes.
3. The client should redirect you to the Microsoft sign-in page. After login,
   the gateway's consent screen renders. Approve it.
4. The client receives an access token and can call `tools/list`.

If something fails partway through, walk the flow manually using the
[manual OAuth testing guide](./manual-oauth-testing.mdx) — it exercises every
endpoint with `curl` so you can see the raw responses.

## Common issues

- **`tenantId` rejected at boot.** The wrapper accepts only a tenant UUID, not a
  verified domain, `common`, `organizations`, or `consumers`. Look up the tenant
  ID under **Overview** in the Entra admin center.
- **`AADSTS50011` redirect URI mismatch.** The redirect URI on the app
  registration doesn't match `https://<gateway-host>/oauth/callback` exactly.
  Match scheme, host, and path.
- **`AADSTS700016` application not found.** The client ID doesn't belong to the
  tenant the wrapper is configured with. Make sure `tenantId` and `clientId`
  come from the same app registration.

## Related

- [Authentication overview](./overview.mdx)
- [Configuring a generic OIDC provider](./configuring-generic-oidc.mdx)
- [Per-user OAuth to upstream MCP servers](./upstream-oauth.mdx)
