> ## Documentation Index
> Fetch the complete documentation index at: https://neuraltrust-92b43583-develop.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Entra ID

> End-to-end manual for wiring Microsoft Entra ID (Azure AD) to TrustGate: an app registration with an exposed API scope and app roles feeding OIDC identity-based routing and OAuth2 for MCP.

This manual sets up **Microsoft Entra ID** (formerly Azure AD) as the identity provider for
two TrustGate patterns in the NeuralTrust app (**Agent Gateway → Identity** /
**Consumers**):

1. **OIDC · Identity-based LLM** — an LLM consumer whose routing is chosen from the
   token's `roles` (app roles) or `groups` claim ([roles](/trustgate/concepts/roles)).
2. **OAuth2 · MCP** — an MCP consumer gated by an exposed API scope. Interactive agents
   (Cursor, etc.) use TrustGate's authorization-code broker; optional client-credentials
   tokens are only for curl checks.

<Note>
  Entra ID issues **v2.0** tokens when the app registration uses the v2 endpoint. TrustGate
  treats an `api://` resource URI and its bare identifier as the same audience, and prefers
  the Entra `oid` claim as the stable subject. Use the values below exactly.
</Note>

## Prerequisites

* An Entra tenant — note your **Tenant ID** (`Overview → Tenant ID`).
* Rights to register applications (**App registrations**) and manage
  **Enterprise applications**.
* A group or set of users you can assign to an app role.
* For Cursor / agent MCP login: the public MCP base URL of your gateway (e.g.
  `https://{gateway_slug}.mcp.neuraltrust.ai`), so you can register the OAuth redirect URI.

<Note>
  **SaaS vs Private `mcp_base_url`:** SaaS uses `{slug}.mcp.neuraltrust.ai`. Private/Hybrid uses the Dataplane MCP URL from **Settings → Agent Gateway → General**. Register `{mcp_base_url}/oauth/callback` on the IdP **before** testing agent login.
</Note>

## The values you will collect

| Placeholder     | Where it comes from                                                        |
| --------------- | -------------------------------------------------------------------------- |
| `tenant_id`     | **Entra ID → Overview → Tenant ID**                                        |
| `client_id`     | The **Application (client) ID** of your app registration                   |
| `client_secret` | **Certificates & secrets → New client secret**                             |
| `audience`      | The **Application ID URI** — `api://{client_id}` (or the bare `client_id`) |
| `scope`         | A scope you expose under **Expose an API** (e.g. `mcp.access`)             |
| `mcp_base_url`  | Public MCP host for the gateway, with no path                              |

The endpoints are derived from the tenant:

```text theme={null}
issuer   : https://login.microsoftonline.com/{tenant_id}/v2.0
jwks_url : https://login.microsoftonline.com/{tenant_id}/discovery/v2.0/keys
```

<Warning>
  If your app is configured for **v1.0** tokens, the issuer is instead
  `https://sts.windows.net/{tenant_id}/` and the JWKS is
  `https://login.microsoftonline.com/common/discovery/keys`. Prefer v2.0.
</Warning>

***

## 1. Register the application

<Note>
  Steps use the **Microsoft Entra admin center** at `https://entra.microsoft.com` (the same
  screens exist in the Azure portal under **Microsoft Entra ID**). Paths below are the
  left-hand navigation.
</Note>

<Steps>
  <Step title="Create the registration">
    In the left sidebar go to **Identity → Applications → App registrations** → click
    **New registration**.

    * **Name**: `trustgate`
    * **Supported account types**: pick the one that matches your tenant.
    * **Redirect URI** (optional at create time; required for interactive MCP): platform
      **Web**, URI `{mcp_base_url}/oauth/callback`\
      (example: `https://default-xxxxxxxx.mcp.neuraltrust.ai/oauth/callback`)

    Click **Register**. On the app's **Overview** page copy the **Application (client) ID**
    and the **Directory (tenant) ID**.
  </Step>

  <Step title="Add a client secret">
    Open the app → left menu **Manage → Certificates & secrets** → **Client secrets** tab →
    **New client secret**. Set a description and expiry, click **Add**, then copy the secret
    **Value** immediately (it is shown only once — the *Secret ID* is not the value).
  </Step>

  <Step title="Confirm the redirect URI (for Cursor / MCP)">
    If you skipped it at registration, open **Manage → Authentication** → **Add a
    platform** → **Web**, and add:

    ```text theme={null}
    {mcp_base_url}/oauth/callback
    ```

    Match character-for-character (no trailing slash). Save.
  </Step>
</Steps>

## 2. Expose an API scope (for MCP)

<Steps>
  <Step title="Set the Application ID URI">
    Open the app → left menu **Manage → Expose an API**. Next to *Application ID URI* click
    **Add**, accept the default `api://{client_id}`, and **Save**. This URI is your
    **audience**.
  </Step>

  <Step title="Add the scope">
    Still on **Expose an API**, click **Add a scope**:

    * **Scope name**: `mcp.access`
    * **Who can consent**: *Admins and users* (or Admins only) as appropriate.
    * Fill the admin/user consent display name and description.
    * **State**: Enabled.

    Click **Add scope**. The full scope identifier is `api://{client_id}/mcp.access`.
  </Step>
</Steps>

## 3. Define app roles (for identity-based LLM routing)

App roles are the cleanest way to drive TrustGate roles; they arrive in the `roles` claim.

<Steps>
  <Step title="Create the app role">
    Open the app → left menu **Manage → App roles** → **Create app role**:

    * **Display name**: `Engineering`
    * **Allowed member types**: *Users/Groups* (and/or *Applications* for M2M).
    * **Value**: `engineering` — this exact string is what appears in the `roles` claim.
    * **Description**: anything.
    * Tick **Do you want to enable this app role?**

    Click **Apply**.
  </Step>

  <Step title="Assign users or groups to the role">
    Assignment happens on the **enterprise application** (the service principal), not the
    registration. In the left sidebar go to **Identity → Applications → Enterprise
    applications** → open your `trustgate` app → **Manage → Users and groups** → **Add
    user/group**. Pick the users/groups, and under **Select a role** choose `Engineering`.
    Click **Assign**.
  </Step>

  <Step title="(Optional) emit a groups claim instead">
    To route on directory groups instead of app roles, open the **app registration** →
    **Manage → Token configuration** → **Add groups claim**, pick the group types, and save.
    The token then carries a `groups` array of group **object IDs** (GUIDs), not names.
  </Step>
</Steps>

***

## 4. OIDC identity-based routing (LLM)

Use this when an LLM consumer should pick registries and models from the caller's Entra
token (app role `roles`, or directory `groups`). Everything below is done in the
NeuralTrust app under **Agent Gateway** — no API payloads.

Recommended order: **Auth → Role → Consumer**.

<Steps>
  <Step title="Create an OIDC auth">
    Go to **Agent Gateway → Identity → Auth → New Auth**.

    * **Type**: `OIDC`
    * **Name**: e.g. `entra-idp`
    * **Status**: Active
    * **Issuer**: `https://login.microsoftonline.com/{tenant_id}/v2.0`
    * **JWKS URL**: `https://login.microsoftonline.com/{tenant_id}/discovery/v2.0/keys`
    * **Audiences**: `api://{client_id}` (add the bare `{client_id}` as well if your
      tokens use that form)
    * **Subject claim**: `oid`\
      (`oid` is a stable per-tenant user id; Entra `sub` is pairwise per app and changes
      across apps)

    Click **Create Auth**.
  </Step>

  <Step title="Create a role that matches Entra app roles">
    Still under **Identity**, open the **Roles** tab → **New Role**.

    * **Role name**: e.g. `engineering`
    * **Claim**: `roles` (or `groups` if you emit directory groups instead)
    * **Value**: the app role **Value** from step 3, e.g. `engineering`\
      (for directory groups, use the group **object ID** GUID, not the display name)
    * **Resources**: **Add resource** and select the LLM registries (and optional models)
      this role may reach

    Click **Create Role**.
  </Step>

  <Step title="Create an LLM consumer with Identity-based routing">
    Go to **Agent Gateway → Consumers → Consumer**.

    On **General**:

    * **Name**: e.g. `entra-llm`
    * **Protocol**: `LLM`
    * **Authentication → Method**: `OIDC`
    * **OIDC provider**: select the auth from the first step

    On **Routing**:

    * Switch from **Static** to **Identity-based**
    * **Roles**: select the role(s) you created (at least one)

    Click **Create Consumer**.

    For an existing consumer, use the **Auth** and **Routing** tabs the same way, then
    **Save changes**. With Identity-based routing, registries come from the role's
    resources — not from a static list on the consumer.
  </Step>
</Steps>

## 5. OAuth2 for MCP

Use this when Cursor (or another MCP client) should log in through Entra. Access is gated
by the exposed API scope (for example `mcp.access`).

TrustGate accepts either:

* **Delegated (user) tokens** — scope appears in the `scp` claim (what Cursor uses via the
  authorization-code broker).
* **Application (client credentials) tokens** — grant an **app role** as an application
  permission, request `api://{client_id}/.default`; the granted role appears in `roles`.
  TrustGate matches both `scp` and `roles` against **Required scopes**.

<Steps>
  <Step title="Grant API permission (client-credentials / M2M only)">
    Skip this step for Cursor. For curl M2M tests: open the **calling** app registration →
    **Manage → API permissions** → **Add a permission** → **My APIs** → select your
    `trustgate` API → **Application permissions** → tick the app role → **Add
    permissions**. Then **Grant admin consent for your tenant**. Without admin consent the
    token carries no `roles` and TrustGate rejects it for missing scopes.
  </Step>

  <Step title="Create an OAuth2 auth in the app">
    Go to **Agent Gateway → Identity → Auth → New Auth**.

    * **Type**: `OAuth2`
    * **Name**: e.g. `entra-mcp`
    * **Status**: Active
    * **Setup**: **Interactive login · IdP with discovery (Okta, Entra ID)**
    * **Issuer**: `https://login.microsoftonline.com/{tenant_id}/v2.0`
    * **Audiences**: `api://{client_id}`
    * **Client ID** / **Client secret**: from the app registration
    * **Session mode**: **Disabled**
    * **Required scopes**: `mcp.access` (the short scope name — not
      `openid` / `profile` / `email`)

    Leave **Token validation · advanced** closed unless you want to pin **JWKS URL** to
    `https://login.microsoftonline.com/{tenant_id}/discovery/v2.0/keys`.

    Click **Create Auth**.
  </Step>

  <Step title="Create an MCP consumer and attach the auth">
    Go to **Agent Gateway → Consumers → Consumer**.

    * **Name**: e.g. `entra-mcp`
    * **Protocol**: `MCP`
    * **Authentication → Method**: `OAuth2` (MCP does not use OIDC)
    * **OAuth client**: select the auth from the previous step

    Click **Create Consumer**.
  </Step>

  <Step title="Connect from Cursor">
    Open the consumer → **Connect** tab and copy the Cursor URL:

    ```text theme={null}
    https://{mcpHost}/{consumer_slug}/mcp
    ```

    Add it as an MCP server in Cursor and complete the Entra login. Confirm the Web
    redirect URI from step 1 is registered, or Entra will reject the callback.
  </Step>
</Steps>

## 6. Get a test token (optional M2M)

Client credentials are **not** what Cursor uses. Use this only to decode a token and
confirm issuer, audience, and scopes:

```bash theme={null}
curl -s --request POST \
  "https://login.microsoftonline.com/$TENANT_ID/oauth2/v2.0/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=$CLIENT_ID" \
  -d "client_secret=$CLIENT_SECRET" \
  -d "scope=api://$CLIENT_ID/.default"
```

Decode the `access_token` and confirm:

* `iss` = `https://login.microsoftonline.com/{tenant_id}/v2.0`
* `aud` = `api://{client_id}` (or the bare `client_id`)
* `roles` (app permissions) or `scp` (delegated) contains your scope/role

## Troubleshooting

| Symptom                        | Likely cause                                                                                                                                                                 |
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Audience mismatch              | Token `aud` is the bare `client_id` but the auth lists only `api://…` (or vice versa). Add both forms to **Audiences** if unsure — TrustGate normalizes the `api://` prefix. |
| `roles`/`scp` empty            | For client credentials you must **grant an app role** and admin-consent it; delegated tokens need the user assigned and consent granted.                                     |
| Redirect URI mismatch on login | Add `{mcp_base_url}/oauth/callback` under **Authentication → Web** on the app registration.                                                                                  |
| Wrong issuer                   | App is emitting v1.0 tokens (`sts.windows.net`) — switch to v2 or set the v1 issuer/JWKS.                                                                                    |
| Role never selected            | `groups` claim carries object IDs, not names — put the GUID in the role **Value**, or match app-role `roles` values instead.                                                 |
| Config rejected                | **Required scopes** contains `openid`/`profile`/`email`/`offline_access` — remove them.                                                                                      |

Back to [Auth](/trustgate/concepts/auth).
