|
| 1 | +--- |
| 2 | +title: Secure MCP servers on Azure Container Apps |
| 3 | +description: Learn how to authenticate and authorize MCP servers on Azure Container Apps using Microsoft Entra ID or API key authentication. |
| 4 | +#customer intent: As a developer, I want to secure my MCP server on Azure Container Apps so that only authorized clients can access my tools. |
| 5 | +ms.topic: how-to |
| 6 | +ms.service: azure-container-apps |
| 7 | +ms.collection: ce-skilling-ai-copilot |
| 8 | +ms.date: 02/19/2026 |
| 9 | +author: craigshoemaker |
| 10 | +ms.author: cshoe |
| 11 | +ms.reviewer: cshoe |
| 12 | +--- |
| 13 | + |
| 14 | +# Secure MCP servers on Azure Container Apps |
| 15 | + |
| 16 | +This article explains how to authenticate and secure MCP servers running on Azure Container Apps. The approach differs depending on whether you host a **standalone container app** or use the **platform-managed MCP server** in dynamic sessions. |
| 17 | + |
| 18 | +## Prerequisites |
| 19 | + |
| 20 | +- An Azure account with an active subscription. [Create one for free](https://azure.microsoft.com/free/). |
| 21 | +- [Azure CLI](/cli/azure/install-azure-cli) version 2.62.0 or later. |
| 22 | +- An existing container app or session pool. If you don't have one, see the [MCP server tutorials](mcp-overview.md). |
| 23 | + |
| 24 | +## Authentication models overview |
| 25 | + |
| 26 | +Azure Container Apps supports two authentication models for MCP servers. The following table summarizes the key differences. |
| 27 | + |
| 28 | +| Aspect | Standalone container app | Dynamic sessions MCP | |
| 29 | +|--------|--------------------------|----------------------| |
| 30 | +| Auth mechanism | Container Apps built-in authentication with Microsoft Entra ID | API key via `x-ms-apikey` header | |
| 31 | +| Token type | OAuth 2.0 Bearer token | Opaque API key string | |
| 32 | +| Identity provider | Microsoft Entra ID | Azure Resource Manager | |
| 33 | +| Key/token rotation | Managed by Microsoft Entra ID | Regenerate via Azure Resource Manager API | |
| 34 | +| Authorization scope | Configurable per application | Session pool level | |
| 35 | +| Transport encryption | TLS (Container Apps ingress) | TLS (Container Apps sessions endpoint) | |
| 36 | + |
| 37 | +## Standalone container app with Microsoft Entra ID authentication |
| 38 | + |
| 39 | +When you deploy your own MCP server as a container app, you own the authentication layer. Use the Container Apps [built-in authentication](/azure/container-apps/authentication) feature backed by Microsoft Entra ID. |
| 40 | + |
| 41 | +### Configure built-in authentication |
| 42 | + |
| 43 | +The following steps register a Microsoft Entra ID application and enable built-in authentication on your container app. |
| 44 | + |
| 45 | +1. Register an application in Microsoft Entra ID: |
| 46 | + |
| 47 | + ```azurecli |
| 48 | + APP_ID=$(az ad app create \ |
| 49 | + --display-name "mcp-server-auth" \ |
| 50 | + --sign-in-audience AzureADMyOrg \ |
| 51 | + --query appId -o tsv) |
| 52 | + ``` |
| 53 | +
|
| 54 | +1. Create a service principal: |
| 55 | +
|
| 56 | + ```azurecli |
| 57 | + az ad sp create --id $APP_ID |
| 58 | + ``` |
| 59 | +
|
| 60 | +1. Add a client secret: |
| 61 | +
|
| 62 | + ```azurecli |
| 63 | + CLIENT_SECRET=$(az ad app credential reset --id $APP_ID --query password -o tsv) |
| 64 | + TENANT_ID=$(az account show --query tenantId -o tsv) |
| 65 | + ``` |
| 66 | +
|
| 67 | +1. Enable built-in auth on the container app: |
| 68 | +
|
| 69 | + ```azurecli |
| 70 | + az containerapp auth microsoft update \ |
| 71 | + --name <CONTAINER_APP_NAME> \ |
| 72 | + --resource-group <RESOURCE_GROUP> \ |
| 73 | + --client-id $APP_ID \ |
| 74 | + --client-secret $CLIENT_SECRET \ |
| 75 | + --tenant-id $TENANT_ID \ |
| 76 | + --issuer "https://login.microsoftonline.com/$TENANT_ID/v2.0" \ |
| 77 | + --yes |
| 78 | + ``` |
| 79 | +
|
| 80 | +1. Set the unauthenticated action to require login: |
| 81 | +
|
| 82 | + ```azurecli |
| 83 | + az containerapp auth update \ |
| 84 | + --name <CONTAINER_APP_NAME> \ |
| 85 | + --resource-group <RESOURCE_GROUP> \ |
| 86 | + --unauthenticated-client-action Return401 |
| 87 | + ``` |
| 88 | +
|
| 89 | +### Connect from an MCP client with a bearer token |
| 90 | +
|
| 91 | +When your MCP server requires a bearer token, configure token retrieval in your MCP client. The following example shows a `.vscode/mcp.json` configuration for GitHub Copilot: |
| 92 | +
|
| 93 | +```json |
| 94 | +{ |
| 95 | + "servers": { |
| 96 | + "my-mcp-server": { |
| 97 | + "type": "http", |
| 98 | + "url": "https://<CONTAINER_APP_NAME>.<REGION>.azurecontainerapps.io/mcp", |
| 99 | + "headers": { |
| 100 | + "Authorization": "Bearer ${input:mcpBearerToken}" |
| 101 | + } |
| 102 | + } |
| 103 | + }, |
| 104 | + "inputs": [ |
| 105 | + { |
| 106 | + "id": "mcpBearerToken", |
| 107 | + "type": "promptString", |
| 108 | + "description": "Enter your bearer token for the MCP server", |
| 109 | + "password": true |
| 110 | + } |
| 111 | + ] |
| 112 | +} |
| 113 | +``` |
| 114 | + |
| 115 | +> [!TIP] |
| 116 | +> For development, get a token by using `az account get-access-token --resource $APP_ID --query accessToken -o tsv` and paste it when prompted. For automated workflows, integrate with your organization's token management system. |
| 117 | +
|
| 118 | +### Configure CORS |
| 119 | + |
| 120 | +MCP clients that connect from web-based environments need CORS headers. Use the following command to configure CORS on your container app: |
| 121 | + |
| 122 | +```azurecli |
| 123 | +az containerapp ingress cors update \ |
| 124 | + --name <CONTAINER_APP_NAME> \ |
| 125 | + --resource-group <RESOURCE_GROUP> \ |
| 126 | + --allowed-origins "https://vscode.dev" "https://github.dev" \ |
| 127 | + --allowed-methods "GET" "POST" "OPTIONS" \ |
| 128 | + --allowed-headers "Content-Type" "Authorization" "Mcp-Session-Id" \ |
| 129 | + --max-age 3600 |
| 130 | +``` |
| 131 | + |
| 132 | +The following headers are key to allow: |
| 133 | + |
| 134 | +- `Content-Type`: required for JSON-RPC requests |
| 135 | +- `Authorization`: required for bearer token auth |
| 136 | +- `Mcp-Session-Id`: used by MCP clients for stateful sessions |
| 137 | + |
| 138 | +> [!NOTE] |
| 139 | +> GitHub Copilot connects to remote MCP servers from the VS Code desktop app, not from a browser. CORS is only needed if you intend to support browser-based MCP clients or VS Code for the Web. The standalone tutorials use wildcard CORS origins for simplicity; for production, restrict to specific trusted origins as shown here. |
| 140 | +
|
| 141 | +### Security recommendations for standalone MCP servers |
| 142 | + |
| 143 | +Apply the following best practices to harden your standalone MCP server. |
| 144 | + |
| 145 | +- **Network restrictions**: Use [IP restrictions](/azure/container-apps/ip-restrictions) or [virtual network integration](/azure/container-apps/vnet-custom) to limit access to known client IPs. |
| 146 | +- **Rate limiting**: Implement rate limiting in your application code or front the app with Azure API Management. |
| 147 | +- **Input validation**: Validate all tool arguments in your MCP server code. MCP tool inputs are arbitrary JSON. Treat them as untrusted. |
| 148 | +- **Stateless design**: Prefer stateless MCP servers to avoid session-hijacking risks. In most MCP SDKs, this means disabling server-side session ID generation (for example, `sessionIdGenerator: undefined` in TypeScript or `stateless_http=True` in Python). |
| 149 | +- **Health probes**: Configure health probes on a separate endpoint (such as `/healthz`), not on the MCP endpoint. MCP endpoints expect JSON-RPC POST requests and return errors for plain GET probes. |
| 150 | + |
| 151 | +## Dynamic sessions with API key authentication |
| 152 | + |
| 153 | +> [!IMPORTANT] |
| 154 | +> The platform-managed MCP server for dynamic sessions is in **preview**. The API version `2025-02-02-preview` and `mcpServerSettings` properties are subject to change. |
| 155 | +
|
| 156 | +The platform-managed MCP server in dynamic sessions uses API key authentication. The key is scoped to the session pool and grants access to all tools and sessions in the pool. |
| 157 | + |
| 158 | +### API key authentication flow |
| 159 | + |
| 160 | +The following steps describe how API key authentication works for dynamic sessions. |
| 161 | + |
| 162 | +1. The client sends a JSON-RPC request with the `x-ms-apikey` header. |
| 163 | +1. The session pool proxy validates the key against the Azure control plane. |
| 164 | +1. If the key is valid, the request is forwarded to the session. If not, an authentication error is returned. |
| 165 | + |
| 166 | +### Retrieve the API key |
| 167 | + |
| 168 | +Use the following command to fetch the API key for your session pool. |
| 169 | + |
| 170 | +```azurecli |
| 171 | +API_KEY=$(az rest --method POST \ |
| 172 | + --uri "https://management.azure.com/subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP>/providers/Microsoft.App/sessionPools/<SESSION_POOL_NAME>/fetchMCPServerCredentials" \ |
| 173 | + --uri-parameters api-version=2025-02-02-preview \ |
| 174 | + --query "apiKey" -o tsv) |
| 175 | +``` |
| 176 | + |
| 177 | +### Rotate and cache the API key |
| 178 | + |
| 179 | +You can regenerate the API key at any time. The platform caches validation results for up to five minutes, so previously valid keys might continue to work after regeneration until the cache expires. |
| 180 | + |
| 181 | +To rotate the API key, call the `regenerateCredentials` action on the session pool: |
| 182 | + |
| 183 | +```azurecli |
| 184 | +az rest --method POST \ |
| 185 | + --uri "https://management.azure.com/subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP>/providers/Microsoft.App/sessionPools/<SESSION_POOL_NAME>/regenerateCredentials" \ |
| 186 | + --uri-parameters api-version=2025-02-02-preview |
| 187 | +``` |
| 188 | + |
| 189 | +After regeneration, retrieve the new key by using `fetchMCPServerCredentials` as shown earlier. |
| 190 | + |
| 191 | +### Security recommendations for dynamic sessions |
| 192 | + |
| 193 | +Apply the following best practices to secure your dynamic sessions MCP deployment. |
| 194 | + |
| 195 | +- **API key scope**: The API key grants access to the entire session pool. Any client with the key can create environments and execute code. Don't share the key with untrusted parties. |
| 196 | +- **Environment isolation**: Each session runs in a Hyper-V-isolated container. Code execution in one session can't access another session's data. |
| 197 | +- **Network egress**: Control whether sessions can access the internet by using `sessionNetworkConfiguration.status`. Set to `EgressDisabled` if the sessions don't need external network access. |
| 198 | +- **Session lifetime**: Configure `coolDownPeriodInSeconds` to automatically destroy idle sessions. This setting limits the window of exposure if a session is compromised. |
| 199 | +- **Secret storage**: Store the API key in [Azure Key Vault](/azure/key-vault/general/overview) or [Container Apps secrets](/azure/container-apps/manage-secrets) rather than in code or configuration files. |
| 200 | + |
| 201 | +## Common authentication mismatches |
| 202 | + |
| 203 | +A common mistake is using the API key header (`x-ms-apikey`) with a standalone container app, or using a bearer token with the sessions MCP endpoint. The following table shows what happens when you mix them up. |
| 204 | + |
| 205 | +| Mismatch | Result | |
| 206 | +|----------|--------| |
| 207 | +| `x-ms-apikey` header sent to standalone app | Header is ignored; request hits built-in authentication and returns `401` if auth is enabled | |
| 208 | +| `Authorization: Bearer` sent to sessions MCP | Key validation fails and returns `401` | |
| 209 | + |
| 210 | +Make sure your MCP client configuration matches the hosting model you deployed. |
| 211 | + |
| 212 | +## Related content |
| 213 | + |
| 214 | +- [MCP servers on Azure Container Apps overview](mcp-overview.md) |
| 215 | +- [Authentication and authorization in Azure Container Apps](/azure/container-apps/authentication) |
| 216 | +- [Microsoft Entra ID authentication](/azure/container-apps/authentication-azure-active-directory) |
| 217 | +- [Dynamic sessions overview](/azure/container-apps/sessions) |
| 218 | +- [Manage secrets in Container Apps](/azure/container-apps/manage-secrets) |
| 219 | +- [Configure CORS for Container Apps](/azure/container-apps/cors) |
| 220 | +- [Troubleshoot MCP servers on Container Apps](mcp-troubleshooting.md) |
0 commit comments