diff --git a/assets/agw-docs/pages/agentgateway/llm/providers/backend-tunnel-proxy.md b/assets/agw-docs/pages/agentgateway/llm/providers/backend-tunnel-proxy.md index 45233d735..da8a207df 100644 --- a/assets/agw-docs/pages/agentgateway/llm/providers/backend-tunnel-proxy.md +++ b/assets/agw-docs/pages/agentgateway/llm/providers/backend-tunnel-proxy.md @@ -46,7 +46,7 @@ This example configures an {{< reuse "agw-docs/snippets/agentgateway.md" >}} bac port: 443 policies: tls: - insecureSkipVerify: true + insecureSkipVerify: All tunnel: backendRef: group: {{< reuse "agw-docs/snippets/group.md" >}} @@ -61,6 +61,6 @@ This example configures an {{< reuse "agw-docs/snippets/agentgateway.md" >}} bac | `spec.static.host` | The upstream backend host (the actual service you want to reach) | | `spec.policies.tunnel.backendRef.name` | The {{< reuse "agw-docs/snippets/backend.md" >}} or `Service` that represents the proxy | | `spec.policies.tunnel.backendRef.port` | The port the proxy listens on | - | `spec.policies.tls.insecureSkipVerify` | Skip TLS certificate validation when connecting to the upstream backend. Use with caution — only in trusted environments. | + | `spec.policies.tls.insecureSkipVerify` | Skip TLS certificate validation when connecting to the upstream backend. Set to `All` to skip all verification or `Hostname` to skip only hostname verification. Use with caution — only in trusted environments. | 3. Agentgateway now routes all outbound connections from `idp-proxied` through the `squid-proxy` tunnel. When the control plane needs to fetch JWKS keys, it does so via the proxy. diff --git a/assets/agw-docs/pages/agentgateway/mcp/mcp-auth-entra.md b/assets/agw-docs/pages/agentgateway/mcp/mcp-auth-entra.md new file mode 100644 index 000000000..cab7c5af9 --- /dev/null +++ b/assets/agw-docs/pages/agentgateway/mcp/mcp-auth-entra.md @@ -0,0 +1,312 @@ +Secure your Model Context Protocol (MCP) servers with OAuth 2.0 authentication by using agentgateway and Microsoft Entra ID (formerly Azure Active Directory) as the identity provider. + +## About this guide + +In this guide, you configure the agentgateway proxy to protect a static MCP server with MCP auth by using Microsoft Entra ID as the authorization server. Because Entra does not fully implement the OAuth behaviors that the [MCP authorization specification](https://modelcontextprotocol.io/specification/draft/basic/authorization) assumes, agentgateway includes a native `Entra` provider that bridges the gaps. When you set `provider: Entra`, agentgateway serves RFC 8414 authorization server metadata from Entra's OIDC discovery document, strips the RFC 8707 `resource` parameter that Entra rejects, and short-circuits Dynamic Client Registration with your pre-registered application (client) ID. + +{{< callout type="warning" >}} +This guide configures Entra app registrations that are **public clients** using PKCE, such as local MCP clients. Confidential clients (app registrations under the Entra **Web** platform) require a client secret at the token endpoint. On Kubernetes, injecting that secret through the recommended `jwtAuthentication.mcp` traffic policy is not yet supported. If you need confidential-client support today, use agentgateway in standalone mode, which accepts a `clientSecret` field on the MCP authentication policy. +{{< /callout >}} + +For more information about MCP auth, see the [About MCP auth]({{< link-hextra path="/mcp/auth/about/" >}}) page. + +## Before you begin + +1. Set up an [agentgateway proxy]({{< link-hextra path="/setup/gateway/" >}}). +2. Follow the steps to set up an [MCP server with a fetch tool]({{< link-hextra path="/mcp/static-mcp/" >}}). +3. Install the experimental channel Gateway API. + ```sh {paths="setup-entra"} + kubectl apply --server-side -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v{{< reuse "agw-docs/versions/k8s-gw-version-exp.md" >}}/experimental-install.yaml + ``` +4. Register an application in Microsoft Entra ID and collect the values that agentgateway needs. + 1. Make sure that you have access to a [Microsoft Entra ID tenant](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-create-new-tenant). If you do not have one, you can create a free tenant for development purposes. + 2. [Register an application](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app) in the Microsoft Entra admin center. For **Supported account types**, choose the option that fits your organization. Under **Redirect URI**, select the **Mobile and desktop applications** platform and add the callback URLs of the MCP clients that you plan to connect. + 3. From the app's **Overview** page, note the **Directory (tenant) ID** and the **Application (client) ID**, and save them as environment variables. + ```bash + export ENTRA_TENANT_ID= + export ENTRA_CLIENT_ID= + ``` + 4. Select **Expose an API**. Next to **Application ID URI**, click **Set** and accept the default value of `api://${ENTRA_CLIENT_ID}`. Then click **Add a scope**, enter a scope name such as `mcp_access`, set **Who can consent** to **Admins and users**, and click **Add scope**. + +{{< doc-test paths="setup-entra" >}} +# The controller fetches the provider's remote JWKS when it translates the policy, +# so the test uses Microsoft's real multi-tenant `common` endpoint +# (https://login.microsoftonline.com/common/discovery/v2.0/keys) to resolve keys +# without a dedicated tenant. The client ID is a placeholder; it is not validated +# for the unauthenticated requests the test makes. Replace both with your real +# tenant and app registration IDs when you follow the guide. +export ENTRA_TENANT_ID="${ENTRA_TENANT_ID:-common}" +export ENTRA_CLIENT_ID="${ENTRA_CLIENT_ID:-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee}" +{{< /doc-test >}} + +## Create the JWKS backend + +Create a {{< reuse "agw-docs/snippets/backend.md" >}} that points to the Microsoft login endpoint, and a BackendTLSPolicy that originates a TLS connection to it. The JWT authentication policy uses this backend to fetch Entra's public keys for token signature validation. + +1. Create an {{< reuse "agw-docs/snippets/backend.md" >}} for the Microsoft login endpoint. + ```yaml {paths="setup-entra"} + kubectl apply -f- <}} + kind: {{< reuse "agw-docs/snippets/backend.md" >}} + metadata: + name: entra-jwks + spec: + static: + host: login.microsoftonline.com + port: 443 + EOF + ``` + +2. Create a BackendTLSPolicy that originates a TLS connection to the `entra-jwks` backend by using well-known trusted CA certificates. + ```yaml {paths="setup-entra"} + kubectl apply -f- <}} + group: agentgateway.dev + validation: + hostname: login.microsoftonline.com + wellKnownCACertificates: System + EOF + ``` + +## Configure MCP auth + +With your MCP backend configured, create an {{< reuse "agw-docs/snippets/policy.md" >}} that enforces Entra authentication for the MCP backend. + +1. Create an {{< reuse "agw-docs/snippets/policy.md" >}} with MCP authentication configuration. MCP authentication is configured at the route level by using `traffic.jwtAuthentication` with the `mcp` extension field. The route-level placement aligns MCP auth with standard JWT authentication and lets you use JWT claims in other route-level policies, such as authorization, rate limiting, and transformations. + ```yaml {paths="setup-entra"} + kubectl apply -f - <}} + kind: {{< reuse "agw-docs/snippets/policy.md" >}} + metadata: + name: mcp-entra-authn + spec: + # Target the HTTPRoute to apply authentication at the route level + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: mcp + traffic: + jwtAuthentication: + mode: Strict + providers: + # v2 issuer form. The v1 form https://sts.windows.net// is + # also supported; use it when the app registration mints v1 tokens. + - issuer: "https://login.microsoftonline.com/${ENTRA_TENANT_ID}/v2.0" + # List both the api:// and bare audience + # formats to accept the aud claim that Entra mints for v1 and v2 tokens. + audiences: + - "api://${ENTRA_CLIENT_ID}" + - "${ENTRA_CLIENT_ID}" + jwks: + remote: + backendRef: + name: entra-jwks + kind: {{< reuse "agw-docs/snippets/backend.md" >}} + group: agentgateway.dev + port: 443 + jwksPath: "/${ENTRA_TENANT_ID}/discovery/v2.0/keys" + mcp: + # Use the native Entra provider to bridge Entra's OAuth behaviors + provider: Entra + # Entra has no Dynamic Client Registration, so the gateway answers + # registration requests with this pre-registered client ID. + clientId: "${ENTRA_CLIENT_ID}" + resourceMetadata: + resource: http://localhost:8080/mcp + scopesSupported: + - "api://${ENTRA_CLIENT_ID}/mcp_access" + bearerMethodsSupported: + - header + EOF + ``` + + {{< reuse "agw-docs/snippets/review-table.md" >}} For more information about the `traffic.jwtAuthentication` field, see the [API docs]({{< link-hextra path="/reference/api/#jwtauthentication" >}}). + + | Setting | Description | + | -- | -- | + | `providers[].issuer` | The Entra token issuer URL. Use the v2 form `https://login.microsoftonline.com//v2.0` or the v1 form `https://sts.windows.net//`, depending on which version your app registration mints. This value must match the `iss` claim in the token. | + | `providers[].audiences` | The accepted audiences. List both `api://` and the bare `` to accept the `aud` claim formats that Entra mints for v1 and v2 tokens. | + | `providers[].jwks.remote.backendRef` | The `entra-jwks` backend that points to `login.microsoftonline.com`. | + | `providers[].jwks.remote.jwksPath` | The path to Entra's JWKS endpoint for your tenant. | + | `mcp.provider` | The identity provider. Set to `Entra` to enable the native Entra bridging behavior. | + | `mcp.clientId` | The Application (client) ID of your Entra app registration. Because Entra has no Dynamic Client Registration, agentgateway answers registration requests with this value. | + | `mcp.resourceMetadata` | MCP OAuth resource metadata for discovery. Includes the resource identifier, supported scopes, and bearer token methods. | + +2. Verify that the policy was accepted. + ```sh {paths="setup-entra"} + kubectl get {{< reuse "agw-docs/snippets/policy.md" >}} mcp-entra-authn -o yaml + ``` + +{{< doc-test paths="setup-entra" >}} +YAMLTest -f - <<'EOF' +- name: wait for entra-jwks BackendTLSPolicy to be accepted + wait: + target: + kind: BackendTLSPolicy + metadata: + namespace: default + name: entra-jwks + jsonPath: "$.status.ancestors[0].conditions[?(@.type=='Accepted')].status" + jsonPathExpectation: + comparator: equals + value: "True" + polling: + timeoutSeconds: 60 + intervalSeconds: 2 +- name: wait for mcp-entra-authn policy to be accepted + wait: + target: + kind: AgentgatewayPolicy + metadata: + namespace: default + name: mcp-entra-authn + jsonPath: "$.status.ancestors[0].conditions[?(@.type=='Accepted')].status" + jsonPathExpectation: + comparator: equals + value: "True" + polling: + timeoutSeconds: 60 + intervalSeconds: 2 +EOF +{{< /doc-test >}} + +3. Update the HTTPRoute that routes incoming traffic to the MCP server to include the discovery paths for the MCP resource and authorization server. This way, the agentgateway proxy can retrieve the resource and authorization server metadata during the MCP auth flow. The authorization server path uses a prefix match so that the proxy can serve the bridged Entra metadata and proxy the `authorize` and `token` endpoints under it. + ```yaml {paths="setup-entra"} + kubectl apply -f - <}} + rules: + - filters: + - type: CORS + cors: + allowCredentials: true + allowHeaders: + - Origin + - Authorization + - Content-Type + allowMethods: + - "*" + allowOrigins: + - "*" + exposeHeaders: + - Origin + - X-HTTPRoute-Header + maxAge: 86400 + backendRefs: + - group: agentgateway.dev + kind: {{< reuse "agw-docs/snippets/backend.md" >}} + name: mcp-backend + matches: + # Main MCP endpoint to connect to the MCP server + - path: + type: PathPrefix + value: /mcp + # Path to access resource server metadata + - path: + type: PathPrefix + value: /.well-known/oauth-protected-resource/mcp + # Path to access the bridged authorization server metadata and proxied endpoints + - path: + type: PathPrefix + value: /.well-known/oauth-authorization-server/mcp + EOF + ``` + +{{< doc-test paths="setup-entra" >}} +# WHAT THIS TEST VALIDATES: +# * The Entra MCP auth resources (entra-jwks backend + BackendTLSPolicy, the +# mcp-entra-authn AgentgatewayPolicy with provider: Entra and clientId, and the +# updated HTTPRoute) are accepted, and the provider's JWKS resolves from the +# Entra `common` endpoint so the policy programs on the data plane. +# * The gateway enforces the connect-time 401 challenge and serves the +# protected-resource metadata. +# WHAT THIS TEST DOES NOT VALIDATE (and why): +# * The full interactive OAuth sign-in flow requires a real user signing in to a +# configured Entra app registration, which an automated test cannot perform. +YAMLTest -f - <<'EOF' +- name: wait for mcp HTTPRoute to be accepted + wait: + target: + kind: HTTPRoute + metadata: + namespace: default + name: mcp + jsonPath: "$.status.parents[0].conditions[?(@.type=='Accepted')].status" + jsonPathExpectation: + comparator: equals + value: "True" + polling: + timeoutSeconds: 60 + intervalSeconds: 2 +- name: unauthenticated MCP request returns 401 (connect-time auth enforced) + http: + url: "http://${INGRESS_GW_ADDRESS}:80/mcp" + method: GET + source: + type: local + expect: + statusCode: 401 + headers: + - name: www-authenticate + comparator: contains + value: resource_metadata + retries: 3 +- name: resource metadata discovery returns 200 + http: + url: "http://${INGRESS_GW_ADDRESS}:80/.well-known/oauth-protected-resource/mcp" + method: GET + source: + type: local + expect: + statusCode: 200 + bodyJsonPath: + - path: "$.resource" + comparator: contains + value: "/mcp" + retries: 3 +EOF +{{< /doc-test >}} + +## Verify MCP auth + +Verify the auth flow with the [MCP inspector](https://github.com/modelcontextprotocol/inspector). Because the flow redirects you to Microsoft to sign in, this verification is interactive and requires a live Entra tenant. + +1. Open the MCP inspector. + ```sh + npx @modelcontextprotocol/inspector@{{% reuse "agw-docs/versions/mcp-inspector.md" %}} + ``` + +2. From the MCP Inspector menu, connect to your agentgateway address: + * **Transport Type**: Select `Streamable HTTP`. + * **URL**: Enter the agentgateway address and the `/mcp` path. For a LoadBalancer, use `http://${INGRESS_GW_ADDRESS}/mcp`. For a port-forwarded proxy, use `http://localhost:8080/mcp`. + * Click **Connect**. Verify that the connection fails because authentication is required. + +3. Click **Open Auth Settings** and run through the OAuth flow. During the flow, the MCP inspector discovers the bridged authorization server metadata, registers with your pre-configured `clientId`, and redirects you to Microsoft to sign in. After you sign in and the token is issued, agentgateway validates the Entra token and completes the connection. + +4. Verify that tool calls work without re-authentication. From the **Tools** tab, click **List Tools**, select the `fetch` tool, enter a URL such as `https://example.com/`, and click **Run Tool**. The call succeeds because the token from the initial connection is reused for all tool calls within the session. + +## Clean up + +{{< reuse "agw-docs/snippets/cleanup.md" >}} + +```sh +kubectl delete {{< reuse "agw-docs/snippets/policy.md" >}} mcp-entra-authn +kubectl delete backendtlspolicy entra-jwks +kubectl delete {{< reuse "agw-docs/snippets/backend.md" >}} entra-jwks +kubectl delete httproute mcp +``` diff --git a/assets/agw-docs/pages/integrations/vllm-semantic-router.md b/assets/agw-docs/pages/integrations/vllm-semantic-router.md index 971b66502..a09f96349 100644 --- a/assets/agw-docs/pages/integrations/vllm-semantic-router.md +++ b/assets/agw-docs/pages/integrations/vllm-semantic-router.md @@ -12,7 +12,7 @@ The following diagram shows the [cost-based routing example](/blog/2026-07-17-se The request follows these component boundaries: 1. A client sends a supported request to agentgateway. -2. An AgentgatewayPolicy calls vSR as an external processor during the `PreRouting` phase. +2. An {{< reuse "agw-docs/snippets/policy.md" >}} calls vSR as an external processor during the `PreRouting` phase. 3. vSR evaluates its semantic, complexity, keyword, context, and structure signals. It returns the selected model in its processing response. 4. Agentgateway applies the routing decision and forwards the request to the configured provider or inference workload. 5. Agentgateway records the requested and selected models alongside usage, latency, and optional catalog-priced cost data. diff --git a/content/docs/kubernetes/main/mcp/auth/entra.md b/content/docs/kubernetes/main/mcp/auth/entra.md new file mode 100644 index 000000000..dcc7e6721 --- /dev/null +++ b/content/docs/kubernetes/main/mcp/auth/entra.md @@ -0,0 +1,17 @@ +--- +title: Set up Microsoft Entra ID +weight: 50 +description: Configure Microsoft Entra ID (Azure AD) as an OAuth identity provider for MCP authentication with agentgateway. +test: + setup-entra: + - file: ${versionRoot}/install/helm.md + path: experimental + - file: ${versionRoot}/setup/gateway.md + path: all + - file: ${versionRoot}/mcp/static-mcp.md + path: setup-mcp-server + - file: ${versionRoot}/mcp/auth/entra.md + path: setup-entra +--- + +{{< reuse "agw-docs/pages/agentgateway/mcp/mcp-auth-entra.md" >}} diff --git a/content/docs/standalone/main/configuration/security/mcp-authn.md b/content/docs/standalone/main/configuration/security/mcp-authn.md index 22fdf17d1..080334a74 100644 --- a/content/docs/standalone/main/configuration/security/mcp-authn.md +++ b/content/docs/standalone/main/configuration/security/mcp-authn.md @@ -41,6 +41,8 @@ EOF Agentgateway can adapt traffic for authorization servers that don't fully comply with OAuth standards. For example, Keycloak exposes certificates at a non-standard endpoint. +Set the `provider` field to adapt agentgateway's behavior to a specific authorization server. Supported values include `keycloak`, `auth0`, `okta`, `descope`, `authentik`, and `entra`. Microsoft Entra ID requires extra handling; see [Microsoft Entra ID](#microsoft-entra-id). + In this mode, agentgateway: - Exposes protected resource metadata on behalf of the MCP server - Proxies authorization server metadata and client registration @@ -208,6 +210,175 @@ EOF agentgateway -f proxy-routing.yaml --validate-only {{< /doc-test >}} +## Microsoft Entra ID + +Microsoft Entra ID (Azure AD) does not fully implement the OAuth behaviors that the MCP authorization specification assumes. It rejects the RFC 8707 `resource` parameter that MCP clients send (`AADSTS9010010`), has no Dynamic Client Registration (RFC 7591), and serves only OIDC discovery instead of RFC 8414 authorization server metadata. Set `provider.entra` to have agentgateway bridge these gaps so that you can use Entra as the authorization server without an external adapter proxy. + +With the `entra` provider, agentgateway uses two extra `mcpAuthentication` fields as follows. + +| Field | Description | +|-------|-------------| +| `clientId` | The Application (client) ID of a pre-registered Entra app registration. Because Entra has no Dynamic Client Registration, agentgateway answers registration requests with this value. | +| `clientSecret` | The client secret of the app registration. Required only for confidential clients (apps registered under the Entra **Web** platform), which Entra requires to authenticate at the token endpoint. Omit it for public (PKCE-only) app registrations. Agentgateway injects it server-side into proxied token requests. | + +The route must also match the `/.well-known/oauth-authorization-server/` path prefix so that agentgateway can serve the bridged metadata and proxy the `authorize` and `token` endpoints. The `jwks` field is optional; when omitted, it defaults to `https://login.microsoftonline.com//discovery/v2.0/keys`. + +{{< tabs >}} +{{< tab name="Simplified (MCP)" >}} +```yaml +# yaml-language-server: $schema=https://agentgateway.dev/schema/config +mcp: + port: 3000 + policies: + mcpAuthentication: + issuer: https://login.microsoftonline.com//v2.0 + audiences: + - api:// + - + provider: + entra: {} + clientId: + clientSecret: + resourceMetadata: + resource: http://localhost:3000/mcp + scopesSupported: + - api:///mcp_access + bearerMethodsSupported: + - header + targets: + - name: tools + stdio: + cmd: npx + args: ["@modelcontextprotocol/server-everything"] +``` +{{< /tab >}} +{{< tab name="Routing-based" >}} +```yaml +# yaml-language-server: $schema=https://agentgateway.dev/schema/config +binds: +- port: 3000 + listeners: + - routes: + - backends: + - mcp: + targets: + - name: tools + stdio: + cmd: npx + args: ["@modelcontextprotocol/server-everything"] + matches: + - path: + exact: /mcp + - path: + exact: /.well-known/oauth-protected-resource/mcp + - path: + pathPrefix: /.well-known/oauth-authorization-server/mcp + policies: + mcpAuthentication: + issuer: https://login.microsoftonline.com//v2.0 + audiences: + - api:// + - + provider: + entra: {} + clientId: + clientSecret: + resourceMetadata: + resource: http://localhost:3000/mcp + scopesSupported: + - api:///mcp_access + bearerMethodsSupported: + - header +``` +{{< /tab >}} +{{< /tabs >}} + +For end-to-end setup, including registering the Entra app and connecting an MCP client, see the [Microsoft Entra ID integration guide]({{< link-hextra path="/integrations/auth/entra" >}}). + +{{< doc-test paths="mcp-authn" >}} +# WHAT THIS TEST VALIDATES: +# * The Microsoft Entra ID mcpAuthentication example (entra provider, clientId, +# clientSecret, dual audiences, and the oauth-authorization-server pathPrefix +# match) is accepted by agentgateway in both the simplified MCP (mcp) and +# routing-based (binds) forms. +# * The test points jwks at a local file instead of the derived Entra URL so it +# runs without a live identity provider. +# WHAT THIS TEST DOES NOT VALIDATE (and why): +# * The metadata bridging, resource-strip proxying, DCR short-circuit, and +# token verification — require a live Entra tenant and MCP client that the +# page does not stand up. +cat <<'EOF' > entra-mcp.yaml +# yaml-language-server: $schema=https://agentgateway.dev/schema/config +mcp: + port: 3000 + policies: + mcpAuthentication: + issuer: https://login.microsoftonline.com/11111111-2222-3333-4444-555555555555/v2.0 + audiences: + - api://client-id-guid + - client-id-guid + provider: + entra: {} + clientId: client-id-guid + clientSecret: s3cret + jwks: + file: ./manifests/jwt/pub-key + resourceMetadata: + resource: http://localhost:3000/mcp + scopesSupported: + - api://client-id-guid/mcp_access + bearerMethodsSupported: + - header + targets: + - name: tools + stdio: + cmd: npx + args: ["@modelcontextprotocol/server-everything"] +EOF +agentgateway -f entra-mcp.yaml --validate-only + +cat <<'EOF' > entra-routing.yaml +# yaml-language-server: $schema=https://agentgateway.dev/schema/config +binds: +- port: 3000 + listeners: + - routes: + - backends: + - mcp: + targets: + - name: tools + stdio: + cmd: npx + args: ["@modelcontextprotocol/server-everything"] + matches: + - path: + exact: /mcp + - path: + exact: /.well-known/oauth-protected-resource/mcp + - path: + pathPrefix: /.well-known/oauth-authorization-server/mcp + policies: + mcpAuthentication: + issuer: https://login.microsoftonline.com/11111111-2222-3333-4444-555555555555/v2.0 + audiences: + - api://client-id-guid + - client-id-guid + provider: + entra: {} + clientId: client-id-guid + clientSecret: s3cret + jwks: + file: ./manifests/jwt/pub-key + resourceMetadata: + resource: http://localhost:3000/mcp + scopesSupported: + - api://client-id-guid/mcp_access + bearerMethodsSupported: + - header +EOF +agentgateway -f entra-routing.yaml --validate-only +{{< /doc-test >}} + ## Resource Server Only Agentgateway acts solely as a resource server, validating tokens issued by an external authorization server. diff --git a/content/docs/standalone/main/integrations/auth/entra.md b/content/docs/standalone/main/integrations/auth/entra.md new file mode 100644 index 000000000..4cb6931eb --- /dev/null +++ b/content/docs/standalone/main/integrations/auth/entra.md @@ -0,0 +1,204 @@ +--- +title: Microsoft Entra ID +weight: 60 +description: Protect MCP servers with Microsoft Entra ID (Azure AD) as the authorization server. +--- + +[Microsoft Entra ID](https://learn.microsoft.com/en-us/entra/identity-platform/) is Microsoft's cloud identity platform. Agentgateway includes a native `entra` MCP authentication provider so that you can use Entra as the authorization server for your MCP servers, even though Entra does not fully implement the OAuth behaviors that the [MCP authorization specification](https://modelcontextprotocol.io/specification/draft/basic/authorization) assumes. + +## Why the Entra provider is needed {#why} + +MCP clients such as Claude follow the MCP authorization spec, which relies on OAuth features that Entra handles differently. Without the `entra` provider, you would need to run a separate adapter proxy in front of agentgateway to bridge these gaps. + +Entra deviates from the MCP spec in three main ways: + +- **It rejects the RFC 8707 `resource` parameter.** MCP clients are required to send `resource`, but Entra's v2.0 endpoints reject it alongside v2-style scopes with `AADSTS9010010: invalid_target`. +- **No Dynamic Client Registration (RFC 7591).** Entra has no client registration endpoint. +- **No RFC 8414 authorization server metadata.** Entra serves only OIDC discovery (`openid-configuration`), not the `oauth-authorization-server` metadata that MCP clients discover through the gateway. + +When you set `provider.entra`, agentgateway bridges these gaps as follows: + +- Fetches the tenant's v2.0 `openid-configuration` and serves it as RFC 8414 authorization server metadata, injecting `code_challenge_methods_supported: ["S256"]` because Entra supports PKCE but omits it from its discovery document. +- Advertises gateway-proxied `authorize` and `token` endpoints, and strips the `resource` parameter before forwarding requests to Entra. +- Short-circuits Dynamic Client Registration by returning your pre-registered `clientId`. +- Injects a `clientSecret` into proxied token requests for confidential (Web platform) app registrations. + +For the underlying `mcpAuthentication` fields, see [MCP authentication]({{< link-hextra path="/configuration/security/mcp-authn" >}}). + +## Before you begin {#before-you-begin} + +{{< reuse "agw-docs/snippets/install-agentgateway-binary.md" >}} + +## Register an app in Entra ID {#register} + +Register an application in Microsoft Entra ID and collect the values that agentgateway needs. + +1. Make sure that you have access to a [Microsoft Entra ID tenant](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-create-new-tenant). If you do not have one, you can create a free tenant for development purposes. + +2. [Register an application](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app) in the Microsoft Entra admin center. During registration: + - For **Supported account types**, choose the option that fits your organization. For testing, **Accounts in this organizational directory only** is sufficient. + - Under **Redirect URI**, add the callback URLs of the MCP clients that you plan to connect. Choose the platform based on the client type: + - **Mobile and desktop applications** for public clients that use PKCE, such as local MCP clients. Public clients do not require a client secret. + - **Web** for confidential clients. Entra requires a client secret at the token endpoint for Web-platform apps. + + {{< callout type="warning" >}} + Do not use the **Single-page application (SPA)** platform. Entra redeems SPA-issued authorization codes only through browser cross-origin requests (`AADSTS9002327`), which does not work behind the gateway's token proxy. + {{< /callout >}} + +3. After registration, collect the following values from the app's **Overview** page. Save them as environment variables. + + ```bash + export ENTRA_TENANT_ID= + export ENTRA_CLIENT_ID= + ``` + + | Value | Where to find it | Description | + | ----- | ---------------- | ----------- | + | Tenant ID | **Overview** > **Directory (tenant) ID** | The unique identifier for your Entra ID tenant. Agentgateway derives the Entra endpoints from this value. | + | Application (client) ID | **Overview** > **Application (client) ID** | The unique identifier for the registered app. Use this value as `clientId` in your agentgateway config. Tokens issued for this app include the ID as the audience (`aud`) claim, in the `api://` or bare `` format. | + +4. Expose an API so that tokens can be issued for this app. + 1. Go to your app registration and select **Expose an API**. + 2. Next to **Application ID URI**, click **Set** and accept the default value of `api://${ENTRA_CLIENT_ID}`. + 3. Click **Add a scope**. Enter a scope name such as `mcp_access`, set **Who can consent** to **Admins and users**, fill in the display name and description, and click **Add scope**. You reference this scope in the `resourceMetadata.scopesSupported` field of your agentgateway config. + +5. If your app registration uses the **Web** platform (confidential client), create a client secret. + 1. Go to **Certificates & secrets** > **Client secrets** > **New client secret**. + 2. Save the secret **Value** (not the Secret ID). You cannot retrieve this value later. + + ```bash + export ENTRA_CLIENT_SECRET= + ``` + +## Configure agentgateway {#configure} + +Configure the `mcpAuthentication` policy with the `entra` provider. Note the following Entra-specific requirements: + +- The route must match the `/.well-known/oauth-authorization-server/` path prefix so that agentgateway can serve the bridged metadata and proxy the `authorize` and `token` endpoints. +- Set `clientId` to your app registration's Application (client) ID. Agentgateway uses it to short-circuit Dynamic Client Registration. +- List both the `api://` and bare `` audience formats to accept the `aud` claim that Entra mints for v1 and v2 tokens. +- The `jwks` field is optional. When omitted, agentgateway defaults to `https://login.microsoftonline.com//discovery/v2.0/keys`. + +{{< tabs >}} +{{< tab name="Simplified (MCP)" >}} +```yaml +# yaml-language-server: $schema=https://agentgateway.dev/schema/config +mcp: + port: 3000 + policies: + cors: + allowOrigins: ["*"] + allowHeaders: ["*"] + exposeHeaders: ["Mcp-Session-Id"] + mcpAuthentication: + # v2 issuer form. The v1 form https://sts.windows.net// is also + # supported; use it when the app registration mints v1 access tokens. + issuer: https://login.microsoftonline.com//v2.0 + audiences: + - api:// + - + provider: + entra: {} + # Pre-registered app registration (client) id. Entra has no Dynamic Client + # Registration, so the gateway answers registration requests with this id. + clientId: + # Required only for confidential (Web platform) app registrations. + clientSecret: + resourceMetadata: + resource: http://localhost:3000/mcp + scopesSupported: + - api:///mcp_access + bearerMethodsSupported: + - header + targets: + - name: tools + stdio: + cmd: npx + args: ["@modelcontextprotocol/server-everything"] +``` +{{< /tab >}} +{{< tab name="Routing-based" >}} +```yaml +# yaml-language-server: $schema=https://agentgateway.dev/schema/config +binds: +- port: 3000 + listeners: + - routes: + - backends: + - mcp: + targets: + - name: tools + stdio: + cmd: npx + args: ["@modelcontextprotocol/server-everything"] + matches: + - path: + exact: /mcp + - path: + exact: /.well-known/oauth-protected-resource/mcp + - path: + pathPrefix: /.well-known/oauth-authorization-server/mcp + policies: + cors: + allowOrigins: ["*"] + allowHeaders: ["*"] + exposeHeaders: ["Mcp-Session-Id"] + mcpAuthentication: + issuer: https://login.microsoftonline.com//v2.0 + audiences: + - api:// + - + provider: + entra: {} + clientId: + clientSecret: + resourceMetadata: + resource: http://localhost:3000/mcp + scopesSupported: + - api:///mcp_access + bearerMethodsSupported: + - header +``` +{{< /tab >}} +{{< /tabs >}} + +## Public vs. confidential clients {#client-secret} + +Whether you need a `clientSecret` depends on the platform of your Entra app registration: + +- **Public clients** (**Mobile and desktop applications** platform with public client flows enabled) authenticate with PKCE only. Omit `clientSecret`. The flow is pure PKCE end to end. +- **Confidential clients** (**Web** platform) require client authentication at the token endpoint in addition to PKCE (`AADSTS7000218` otherwise). Set `clientSecret` to the secret that you created for your app registration. Agentgateway attaches it server-side, only to `authorization_code` and `refresh_token` requests for the configured `clientId`. + +{{< callout type="info" >}} +`clientSecret` is the credential of your own app registration, not a credential that MCP clients supply. MCP clients always remain public clients that use PKCE: the gateway advertises `token_endpoint_auth_method: none` in the registration response. +{{< /callout >}} + +## Connect an MCP client {#connect} + +Point your MCP client at the gateway's MCP endpoint, for example `http://localhost:3000/mcp`. The client discovers the bridged authorization server metadata, registers with your pre-configured `clientId`, and completes the OAuth 2.1 authorization code flow with PKCE through Entra. After the user signs in, agentgateway validates the Entra-issued token on each request and enforces any additional route policies. + +## Claim-based authorization {#authorization} + +Because MCP authentication runs at the route level, you can use claims from the validated Entra token in an [authorization]({{< link-hextra path="/configuration/security/mcp-authz" >}}) policy. For example, check for an app role or group claim: + +```yaml +policies: + mcpAuthentication: + issuer: https://login.microsoftonline.com//v2.0 + audiences: + - api:// + - + provider: + entra: {} + clientId: + authorization: + rules: + # Check for an app role assigned in Entra ID + - '"mcp.admin" in jwt.roles' +``` + +## Learn more + +- [Microsoft identity platform documentation](https://learn.microsoft.com/en-us/entra/identity-platform/) +- [MCP authentication]({{< link-hextra path="/configuration/security/mcp-authn" >}}) +- [MCP authorization]({{< link-hextra path="/configuration/security/mcp-authz" >}})