Skip to content

Latest commit

 

History

History
223 lines (164 loc) · 7.56 KB

File metadata and controls

223 lines (164 loc) · 7.56 KB

OAuth Microsoft Entra ID Example

This example demonstrates MCP server authorization using Microsoft Entra ID (formerly Azure AD) as the OAuth 2.0 / OpenID Connect provider.

Features

  • JWT token validation with Microsoft Entra ID
  • Microsoft-specific validator/discovery overrides for Entra quirks
  • Protected Resource Metadata (RFC 9728)
  • MCP tools that access Microsoft claims
  • Optional Microsoft Graph API integration

Prerequisites

  1. Azure Subscription with access to Entra ID
  2. App Registration in Azure Portal

Azure Setup

1. Create App Registration

  1. Go to Azure Portal > Entra ID > App registrations
  2. Click New registration
  3. Configure:
    • Name: MCP Server
    • Supported account types: Choose based on your needs
    • Redirect URI: Leave empty for now (this is a resource server)
  4. Click Register

2. Configure the App

After registration:

  1. Copy values for .env:

    • Application (client) IDAZURE_CLIENT_ID
    • Directory (tenant) IDAZURE_TENANT_ID
  2. Expose an API (optional, for custom scopes):

    • Go to Expose an API
    • Set Application ID URI (e.g., api://your-client-id)
    • Add scopes like mcp.read, mcp.write
  3. Create client secret (for Graph API calls):

    • Go to Certificates & secrets
    • Click New client secret
    • Copy the secret value → AZURE_CLIENT_SECRET
  4. API Permissions (for Graph API):

    • Go to API permissions
    • Add Microsoft Graph > Delegated permissions:
      • User.Read (for profile)
      • Mail.Read (for emails, optional)
    • Grant admin consent if required

3. Create a Client App (for testing)

Create a separate app registration for the client:

  1. New registration:

    • Name: MCP Client
    • Redirect URI: http://localhost (Public client/native)
  2. Authentication:

    • Enable Allow public client flows for PKCE
  3. API permissions:

    • Add permission to your MCP Server app's exposed API

Quick Start

  1. Copy environment file:
cp env.example .env
  1. Edit .env with your Azure values:
AZURE_TENANT_ID=your-tenant-id
AZURE_CLIENT_ID=your-client-id
AZURE_CLIENT_SECRET=your-client-secret  # Optional, for Graph API
  1. Start the services:
docker compose up -d
  1. Get an access token:

Using Azure CLI:

# Login
az login

# Get token for your app
TOKEN=$(az account get-access-token \
  --resource api://your-client-id \
  --query accessToken -o tsv)

Or using MSAL / OAuth flow in your client application.

  1. Test the MCP server:
# Get Protected Resource Metadata
curl http://localhost:8000/.well-known/oauth-protected-resource

# Call MCP endpoint without token (should get 401)
curl -i http://localhost:8000/mcp

# Call MCP endpoint with token
curl -X POST http://localhost:8000/mcp \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'

Architecture

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│   MCP Client    │────▶│     Nginx       │────▶│    PHP-FPM      │
│                 │     │   (port 8000)   │     │   MCP Server    │
└─────────────────┘     └─────────────────┘     └─────────────────┘
        │                                               │
        │ Get Token                                     │ Validate JWT
        ▼                                               ▼
┌─────────────────┐                            ┌─────────────────┐
│  Microsoft      │◀───────────────────────────│   JWKS Fetch    │
│  Entra ID       │                            │                 │
└─────────────────┘                            └─────────────────┘
        │
        │ (Optional) Graph API
        ▼
┌─────────────────┐
│   Microsoft     │
│   Graph API     │
└─────────────────┘

Files

  • docker-compose.yml - Docker Compose configuration
  • Dockerfile - PHP-FPM container
  • nginx/default.conf - Nginx configuration
  • env.example - Environment variables template
  • server.php - MCP server with OAuth middleware (uses built-in LenientOidcDiscoveryMetadataPolicy for metadata validation)
  • MicrosoftJwtTokenValidator.php - Example-specific validator for Graph/non-Graph tokens
  • McpElements.php - MCP tools including Graph API integration

Environment Variables

Variable Required Description
AZURE_TENANT_ID Yes Azure AD tenant ID
AZURE_CLIENT_ID Yes Application (client) ID
AZURE_CLIENT_SECRET No Client secret for Graph API calls

Microsoft Token Structure

Microsoft Entra ID tokens include these common claims:

Claim Description
oid Object ID (unique user identifier in tenant)
tid Tenant ID
sub Subject (unique user identifier)
name Display name
preferred_username Usually the UPN
email Email address (if available)
upn User Principal Name

Troubleshooting

"Invalid issuer" error

Microsoft uses different issuer URLs depending on the token flow:

  • v2.0 endpoint (user/delegated flows): https://login.microsoftonline.com/{tenant}/v2.0
  • v1.0 endpoint (client credentials flow): https://sts.windows.net/{tenant}/

This example automatically accepts both formats by configuring multiple issuers in the MicrosoftJwtTokenValidator. Check your token's iss claim to verify which format is being used.

"Invalid audience" error

The aud claim must match AZURE_CLIENT_ID. For v2.0 tokens with custom scopes, the audience might be api://your-client-id.

JWKS fetch fails

Microsoft's JWKS endpoint is public. Ensure your container can reach: https://login.microsoftonline.com/{tenant}/discovery/v2.0/keys

code_challenge_methods_supported missing in discovery metadata

The default StrictOidcDiscoveryMetadataPolicy requires code_challenge_methods_supported. Microsoft Entra ID omits this field despite supporting PKCE with S256. This example uses the built-in LenientOidcDiscoveryMetadataPolicy which accepts missing code_challenge_methods_supported (defaults to S256 downstream).

Graph API errors

  1. Ensure AZURE_CLIENT_SECRET is set
  2. Verify API permissions have admin consent
  3. Check that the user exists in your tenant

Security Notes

  1. Never commit .env files - they contain secrets
  2. Use managed identities in Azure deployments instead of client secrets
  3. Implement proper token refresh in production clients
  4. Validate scopes for sensitive operations
  5. Important: MicrosoftJwtTokenValidator in this example accepts nonce Graph-style tokens via claim checks only (iss/exp/nbf) without signature verification. Treat this as demo-only behavior and replace it with full signature validation for production.

Cleanup

docker compose down -v