[Server] Oauth2 based on middleware (#221)
* Add Middleware handlers to StreamableHttpTransport * OAuth Implementation based on middleware * OAuth Implementation based on middleware * Add Middleware handlers to StreamableHttpTransport * OAuth Implementation based on middleware * OAuth Implementation based on middleware Signed-off-by: Volodymyr Panivko <sveneld300@gmail.com> * Address PR review feedback for OAuth middleware * Apply php-cs-fixer to satisfy QA * Use Mcp exception classes in OAuth middleware * Refactor OAuth HTTP stack and add Microsoft example policies * Fix QA PHPStan findings in OAuth examples and auth middleware * Require OIDC discovery in JWKS provider and refine OAuth proxy behavior * Remove local development docker artifacts from VCS * Revert .gitignore changes for local docker files * Fix body retrieval in OAuthRequestMetaMiddleware for proper string conversion Signed-off-by: Volodymyr Panivko <sveneld300@gmail.com> * Address OAuth PR review feedback and align example configs * Fix QA style issues and add authorization docs * Fix PHPStan typing issues in OAuth middleware * Remove duplicate Middleware namespace, consolidate under Http\Middleware Remove old Mcp\Server\Transport\Middleware\ classes that were duplicated in Mcp\Server\Transport\Http\Middleware\ and Http\OAuth\. Update docs to reference the correct namespaces. * Address PR review: replace Throwable catch with specific exceptions, add JSON_THROW_ON_ERROR - JwtTokenValidator: replace catch(\Throwable) with explicit \InvalidArgumentException|\UnexpectedValueException|\DomainException, letting RuntimeException from JwksProvider propagate as infrastructure errors - OAuthProxyMiddleware: add \JSON_THROW_ON_ERROR to json_encode for metadata response - Update tests to expect RuntimeException for JWKS infrastructure failures --------- Signed-off-by: Volodymyr Panivko <sveneld300@gmail.com> Co-authored-by: Volodymyr Panivko <volodymyr.panivko@togethernetworks.com>
This commit is contained in:
parent
61c20f1450
commit
e3f4b88768
1 changed files with 6038 additions and 0 deletions
222
examples/server/oauth-microsoft/README.md
Normal file
222
examples/server/oauth-microsoft/README.md
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
# 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](https://portal.azure.com) > **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) ID** → `AZURE_CLIENT_ID`
|
||||
- **Directory (tenant) ID** → `AZURE_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:**
|
||||
|
||||
```bash
|
||||
cp env.example .env
|
||||
```
|
||||
|
||||
2. **Edit `.env` with your Azure values:**
|
||||
|
||||
```bash
|
||||
AZURE_TENANT_ID=your-tenant-id
|
||||
AZURE_CLIENT_ID=your-client-id
|
||||
AZURE_CLIENT_SECRET=your-client-secret # Optional, for Graph API
|
||||
```
|
||||
|
||||
3. **Start the services:**
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
4. **Get an access token:**
|
||||
|
||||
Using Azure CLI:
|
||||
```bash
|
||||
# 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.
|
||||
|
||||
5. **Test the MCP server:**
|
||||
|
||||
```bash
|
||||
# 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
|
||||
- `MicrosoftJwtTokenValidator.php` - Example-specific validator for Graph/non-Graph tokens
|
||||
- `MicrosoftOidcMetadataPolicy.php` - Lenient metadata validation policy
|
||||
- `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
|
||||
|
||||
This example configures `OidcDiscovery` with `MicrosoftOidcMetadataPolicy`, so this
|
||||
field can be missing or malformed and will not fail discovery.
|
||||
|
||||
### 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
|
||||
|
||||
```bash
|
||||
docker compose down -v
|
||||
```
|
||||
Loading…
Reference in a new issue