Enterprise-managed authorization for MCP: Entra-governed App Service sample and specification-accurate ID-JAG lab

Enterprise authorization for MCP on Azure App Service

This repository separates two ideas that are easy to conflate:

  1. What you can deploy with Microsoft Entra ID and App Service Authentication today: centrally governed OAuth, protected resource metadata (PRM), known-client preauthorization, Conditional Access, and platform token validation.
  2. What the MCP Enterprise-Managed Authorization (EMA) extension adds: an enterprise identity provider issues an Identity Assertion JWT Authorization Grant (ID-JAG), which a resource authorization server exchanges for an MCP access token without a per-server browser flow.

The first path is deployable to Azure. The second is a local, specification-accurate interoperability lab.

[!IMPORTANT]Microsoft Entra preauthorization is not the EMA ID-JAG extension. Current App Service MCP authorization guidance documents standard OAuth and preauthorization. The stable EMA specification additionally requires RFC 8693 ID-JAG issuance and RFC 7523 assertion exchange.

What is included

Path Purpose Runs where
Entra + App Service Authentication Secure a real MCP endpoint with Entra OAuth, PRM, client allow-listing, and platform token validation Azure App Service
EMA ID-JAG lab Exercise the full client → enterprise IdP → resource authorization server → MCP server flow Local process

The deployed MCP server exposes two read-only tools:

  • whoami: returns the principal that App Service Authentication validated.
  • authorization_model: reports which authorization path protects the deployment and explicitly states that it is not the ID-JAG flow.

The local EMA lab exposes whoami after exchanging a single-use ID-JAG for an audience-restricted MCP access token.

Architecture

Deployable Entra path

MCP client
    │ discover PRM + request API scope
    ▼
Microsoft Entra ID
    │ user/app policy, preauthorization, Conditional Access
    ▼
App Service Authentication
    │ validate issuer, audience, lifetime, tenant, and client application
    ▼
Python MCP server on App Service

This is centrally managed enterprise OAuth. It removes consent prompts for preauthorized clients, but it does not issue or exchange an ID-JAG.

EMA reference path

MCP client
    │ enterprise sign-in assertion
    ▼
Enterprise IdP
    │ RFC 8693: issue short-lived ID-JAG after policy evaluation
    ▼
MCP authorization server
    │ RFC 7523: validate ID-JAG and issue resource-bound access token
    ▼
MCP server

The lab validates the ID-JAG signature, typ, issuer, audience, client, resource, scopes, expiry, and replay identifier before issuing an access token. It deliberately requires the optional resource claim so every issued token is bound to an explicit MCP resource.

Prerequisites

  • Python 3.11 or later
  • Azure CLI
  • Azure Developer CLI
  • jq
  • Permission to create a Microsoft Entra app registration
  • An Azure subscription

The Azure sample defaults to Python 3.14 on Linux App Service and a Basic B1 plan.

Run locally

python3 -m venv .venv
source .venv/bin/activate
python -m pip install -e ".[dev]"
python -m uvicorn ema_app_service.app:app --app-dir src --reload --port 8000

The MCP endpoint is http://localhost:8000/mcp. App Service Authentication is not present locally, so the whoami tool intentionally fails without a simulated platform principal header. The authorization_model tool remains usable.

Run the tests:

python -m ruff check .
python -m pytest

Run the EMA ID-JAG lab

python -m examples.ema_lab.client

Expected result:

{'subject': '[email protected]', 'client_id': 'finance-agent',
 'scopes': ['mcp:whoami'], 'resource': 'http://localhost/mcp'}

The lab runs the stand-in IdP, MCP authorization server, MCP resource server, and client in one process. It uses a clearly marked demo signing key and in-memory token state. It is a protocol reference, not a production identity provider.

Why the SDK is pinned to Git

The current PyPI mcp release, 1.28.1, does not yet contain the identity-assertion client and authorization-server APIs used by the stable EMA extension. This repository pins the official SDK to commit:

2713b53b127afc094dc97d6067df9f69b647661c

Replace the Git pin with a released package version after those APIs ship and the tests pass unchanged.

Deploy to Azure

The included azd/Bicep deployment creates:

  • Linux App Service plan, Basic B1
  • Python 3.14 App Service with system-assigned managed identity
  • App Service Authentication with Microsoft Entra ID
  • MCP protected resource metadata
  • Log Analytics and workspace-based Application Insights with Microsoft Entra-authenticated ingestion through the app's managed identity

The idempotent Entra setup script creates or reuses a dedicated single-tenant app registration, exposes user_impersonation, and preauthorizes:

  • Visual Studio Code: aebc6443-996d-45c2-90f0-388ff96faa56
  • Azure CLI: 04b07795-8ddb-461a-bbee-02f9e1bf7b46

Deploy:

azd auth login
azd env new ema-mcp
azd env set AZURE_SUBSCRIPTION_ID <subscription-id>
azd env set AZURE_LOCATION eastus2
./scripts/configure-entra-app.sh
azd up

The setup script runs explicitly once because azd resolves required Bicep parameters before invoking preprovision. The hook then reruns the same idempotent script on subsequent provisions.

Some enterprise tenants require a Service Tree reference on new app registrations:

azd env set AZURE_SERVICE_MANAGEMENT_REFERENCE <service-tree-id>
./scripts/configure-entra-app.sh
azd up

Get the endpoint:

azd env get-value MCP_URL

Add that URL to .vscode/mcp.json, start the server, and complete the Microsoft sign-in flow.

What App Service Authentication enforces

The Bicep configuration:

  • Requires authentication on /mcp.
  • Returns 401 rather than redirecting nonbrowser clients.
  • Publishes PRM with api://<app-id>/user_impersonation.
  • Accepts only the expected API audience.
  • Allows only the preconfigured VS Code and Azure CLI client applications.
  • Injects the validated principal for the application to inspect.
  • Leaves / and /health public for status and health checks.

The sample never treats a caller-supplied bearer token or arbitrary header as proof of identity. App Service Authentication is the enforcement point; whoami only reports the platform-injected principal.

Conditional Access and governance

Conditional Access is evaluated when Entra issues the resource token. Use tenant policy to require controls such as MFA, compliant devices, trusted locations, or authentication strength for the MCP API.

This sample does not create Conditional Access policies automatically because they are tenant-wide security controls with licensing and rollout implications. An administrator should:

  1. Target the MCP enterprise application or relevant users/groups.
  2. Start in report-only mode.
  3. Review Entra sign-in logs.
  4. Exclude emergency access accounts.
  5. Enable the policy after validating impact.

App Service Authentication gates the server as a whole. Implement tool-specific authorization in application code when different tools require different roles or scopes.

Verify the deployment

After the app has warmed up:

./scripts/verify-deployment.sh

The script checks:

  • PRM is available.
  • An unauthenticated MCP call returns 401.
  • Azure CLI can obtain an API token.
  • The same MCP request succeeds with that token.

Use Visual Studio Code to invoke whoami and confirm the returned tenant, client application, subject, roles, and scopes.

Query requests in Application Insights:

requests
| where timestamp > ago(15m)
| where url contains "/mcp"
| project timestamp, resultCode, name, operation_Id
| order by timestamp desc

Security boundaries

  • Never forward the MCP resource token to a downstream API. Use managed identity or an explicit on-behalf-of flow for downstream access.
  • The local IdP and in-memory authorization server are demonstration components only.
  • ID-JAGs are short-lived and single-use; the lab rejects jti replay.
  • Production validators should verify ID-JAG signatures against the enterprise IdP's published JWKS. The lab's shared HMAC key is demo-only.
  • No refresh token is issued by the EMA authorization server. The enterprise IdP retains control by deciding whether to issue the next ID-JAG.
  • The Basic B1 sample has a public network endpoint protected by authentication. Add private networking and a gateway for production deployments that require network isolation.

Clean up

azd down --purge

azd down removes Azure resources but not the Microsoft Entra app registration created by the preprovision hook. Remove that registration separately after confirming no environment still uses it.

Sources

License

MIT

MCP Server · Populars

MCP Server · New

    healthchainai

    healthchain

    Python SDK for healthcare AI — typed, validated FHIR tools for agents, real-time EHR connectivity, production deployment ✨ 🏥

    Community healthchainai
    deverman

    FocusRelay — Fast Swift OmniFocus MCP Server and CLI for macOS

    Fast native Swift OmniFocus MCP server and CLI for macOS. Let AI assistants safely read, update, complete, and organize tasks and projects through documented Omni Automation APIs.

    Community deverman
    Glade-tool

    GladeKit MCP

    Connect any MCP-compatible AI client (Claude Code, Cursor, Windsurf) to Unity or Godot. 235+ granular tools, an editor aware system prompt, game design document project context, script semantic search, and skill calibration.

    Community Glade-tool
    Consiliency

    Code-Index-MCP

    Code indexing MCP server to provide context to coding agents.

    Community Consiliency
    semihbugrasezer

    seerxo

    AI-powered Etsy product listing generator for Claude Desktop Generate perfect SEO titles, descriptions, and tags in seconds

    Community semihbugrasezer