Skip to main content
Glama
Azure-Samples

Secure Remote MCP Server

authorize.policy.xml13.3 kB
<!-- AUTHORIZE POLICY OAuth 2.0 PKCE authorization endpoint with Entra ID integration. Flow: Client → Consent (if needed) → Entra ID → Callback → Client --> <policies> <inbound> <base /> <!-- Extract all OAuth parameters --> <set-variable name="clientId" value="@((string)context.Request.Url.Query.GetValueOrDefault("client_id", ""))" /> <set-variable name="redirect_uri" value="@((string)context.Request.Url.Query.GetValueOrDefault("redirect_uri", ""))" /> <set-variable name="currentState" value="@((string)context.Request.Url.Query.GetValueOrDefault("state", ""))" /> <set-variable name="mcpScope" value="@((string)context.Request.Url.Query.GetValueOrDefault("scope", ""))" /> <set-variable name="mcpClientCodeChallenge" value="@((string)context.Request.Url.Query.GetValueOrDefault("code_challenge", ""))" /> <set-variable name="mcpClientCodeChallengeMethod" value="@((string)context.Request.Url.Query.GetValueOrDefault("code_challenge_method", ""))" /> <!-- Validate required OAuth parameters --> <choose> <when condition="@(string.IsNullOrEmpty(context.Variables.GetValueOrDefault<string>("clientId")) || string.IsNullOrEmpty(context.Variables.GetValueOrDefault<string>("redirect_uri")) || string.IsNullOrEmpty(context.Variables.GetValueOrDefault<string>("currentState")))"> <return-response> <set-status code="400" reason="Bad Request" /> <set-header name="Content-Type" exists-action="override"> <value>application/json</value> </set-header> <set-header name="Cache-Control" exists-action="override"> <value>no-store, no-cache</value> </set-header> <set-body>@{ return new JObject { ["error"] = "invalid_request", ["error_description"] = "Missing required parameters: client_id, redirect_uri, and state are all required for OAuth authorization" }.ToString(); }</set-body> </return-response> </when> </choose> <!-- Validate required PKCE parameters --> <choose> <when condition="@(string.IsNullOrEmpty(context.Variables.GetValueOrDefault<string>("mcpClientCodeChallenge")) || string.IsNullOrEmpty(context.Variables.GetValueOrDefault<string>("mcpClientCodeChallengeMethod")))"> <return-response> <set-status code="400" reason="Bad Request" /> <set-header name="Content-Type" exists-action="override"> <value>application/json</value> </set-header> <set-header name="Cache-Control" exists-action="override"> <value>no-store, no-cache</value> </set-header> <set-body>@{ return new JObject { ["error"] = "invalid_request", ["error_description"] = "Missing required PKCE parameters: code_challenge and code_challenge_method are required for secure authorization" }.ToString(); }</set-body> </return-response> </when> </choose> <!-- Normalize redirect URI --> <set-variable name="normalized_redirect_uri" value="@{ string redirectUri = context.Variables.GetValueOrDefault<string>("redirect_uri", ""); if (string.IsNullOrEmpty(redirectUri)) { return ""; } try { string decodedUri = System.Net.WebUtility.UrlDecode(redirectUri); return decodedUri; } catch (Exception) { return redirectUri; } }" /> <!-- Check for existing approval cookie --> <set-variable name="has_approval_cookie" value="@{ try { if (string.IsNullOrEmpty(context.Variables.GetValueOrDefault<string>("clientId", "")) || string.IsNullOrEmpty(context.Variables.GetValueOrDefault<string>("normalized_redirect_uri", ""))) { return false; } string clientId = context.Variables.GetValueOrDefault<string>("clientId", ""); string redirectUri = context.Variables.GetValueOrDefault<string>("normalized_redirect_uri", ""); string APPROVAL_COOKIE_NAME = "__Host-MCP_APPROVED_CLIENTS"; var cookieHeader = context.Request.Headers.GetValueOrDefault("Cookie", ""); if (string.IsNullOrEmpty(cookieHeader)) { return false; } string[] cookies = cookieHeader.Split(';'); foreach (string cookie in cookies) { string trimmedCookie = cookie.Trim(); if (trimmedCookie.StartsWith(APPROVAL_COOKIE_NAME + "=")) { try { string cookieValue = trimmedCookie.Substring(APPROVAL_COOKIE_NAME.Length + 1); string decodedValue = System.Text.Encoding.UTF8.GetString( System.Convert.FromBase64String(cookieValue)); JArray approvedClients = JArray.Parse(decodedValue); string clientKey = $"{clientId}:{redirectUri}"; foreach (var item in approvedClients) { if (item.ToString() == clientKey) { return true; } } } catch (Exception ex) { // Error parsing approval cookie - ignore and continue } break; } } return false; } catch (Exception ex) { // Error checking approval cookie - return false return false; } }" /> <!-- Check if the client has been approved via secure cookie --> <choose> <when condition="@(context.Variables.GetValueOrDefault<bool>("has_approval_cookie"))"> <!-- Continue with normal flow - client is authorized via secure cookie --> </when> <otherwise> <!-- Redirect to consent page for user approval --> <return-response> <set-status code="302" reason="Found" /> <set-header name="Location" exists-action="override"> <value>@{ string basePath = context.Request.OriginalUrl.Scheme + "://" + context.Request.OriginalUrl.Host + (context.Request.OriginalUrl.Port == 80 || context.Request.OriginalUrl.Port == 443 ? "" : ":" + context.Request.OriginalUrl.Port); string clientId = context.Variables.GetValueOrDefault<string>("clientId"); // Use the normalized (already decoded) redirect_uri to avoid double-encoding string redirectUri = context.Variables.GetValueOrDefault<string>("normalized_redirect_uri"); string state = context.Variables.GetValueOrDefault<string>("currentState"); string codeChallenge = context.Variables.GetValueOrDefault<string>("mcpClientCodeChallenge"); string codeChallengeMethod = context.Variables.GetValueOrDefault<string>("mcpClientCodeChallengeMethod"); // URL encode parameters for the consent redirect URL string encodedClientId = System.Net.WebUtility.UrlEncode(clientId); string encodedRedirectUri = System.Net.WebUtility.UrlEncode(redirectUri); // State parameter: use as-is without additional encoding // context.Request.Url.Query.GetValueOrDefault() preserves the original encoding string encodedState = state; // Code challenge parameters: use as-is since they typically don't need encoding string encodedCodeChallenge = codeChallenge; string encodedCodeChallengeMethod = codeChallengeMethod; return $"{basePath}/consent?client_id={encodedClientId}&redirect_uri={encodedRedirectUri}&state={encodedState}&code_challenge={encodedCodeChallenge}&code_challenge_method={encodedCodeChallengeMethod}"; }</value> </set-header> </return-response> </otherwise> </choose> <set-variable name="codeVerifier" value="@((string)Guid.NewGuid().ToString().Replace("-", ""))" /> <set-variable name="codeChallenge" value="@{ using (var sha256 = System.Security.Cryptography.SHA256.Create()) { var bytes = System.Text.Encoding.UTF8.GetBytes((string)context.Variables.GetValueOrDefault("codeVerifier", "")); var hash = sha256.ComputeHash(bytes); return System.Convert.ToBase64String(hash).TrimEnd('=').Replace('+', '-').Replace('/', '_'); } }" /> <!-- Build the complete Entra ID URL using client's original state --> <set-variable name="authUrl" value="@{ string baseUrl = "https://login.microsoftonline.com/{{EntraIDTenantId}}/oauth2/v2.0/authorize"; string codeChallenge = context.Variables.GetValueOrDefault("codeChallenge", ""); string clientState = context.Variables.GetValueOrDefault("currentState", ""); return $"{baseUrl}?response_type=code&client_id={{EntraIDClientId}}&redirect_uri={{OAuthCallbackUri}}&scope={{OAuthScopes}}&code_challenge={codeChallenge}&code_challenge_method=S256&state={System.Net.WebUtility.UrlEncode(clientState)}"; }" /> <!-- STEP 5: Store authentication data in cache for use in callback --> <!-- Generate a confirmation code to return to the MCP client --> <set-variable name="mcpConfirmConsentCode" value="@((string)Guid.NewGuid().ToString())" /> <!-- Store code verifier for token exchange using client state --> <cache-store-value duration="3600" key="@("CodeVerifier-"+context.Variables.GetValueOrDefault("currentState", ""))" value="@(context.Variables.GetValueOrDefault("codeVerifier", ""))" /> <!-- Map client state to MCP confirmation code for callback --> <cache-store-value duration="3600" key="@((string)context.Variables.GetValueOrDefault("currentState"))" value="@(context.Variables.GetValueOrDefault("mcpConfirmConsentCode", ""))" /> <!-- Store MCP client data --> <cache-store-value duration="3600" key="@($"McpClientAuthData-{context.Variables.GetValueOrDefault("mcpConfirmConsentCode")}")" value="@{ return new JObject{ ["mcpClientCodeChallenge"] = (string)context.Variables["mcpClientCodeChallenge"], ["mcpClientCodeChallengeMethod"] = (string)context.Variables["mcpClientCodeChallengeMethod"], ["mcpClientState"] = (string)context.Variables["currentState"], ["mcpClientScope"] = (string)context.Variables["mcpScope"], ["mcpCallbackRedirectUri"] = (string)context.Variables["normalized_redirect_uri"] }.ToString(); }" /> </inbound> <backend> <base /> </backend> <outbound> <base /> <!-- Return the response with a 302 status code for redirect --> <return-response> <set-status code="302" reason="Found" /> <set-header name="Location" exists-action="override"> <value>@(context.Variables.GetValueOrDefault("authUrl", ""))</value> </set-header> <!-- Add cache control headers to ensure browser follows redirect --> <set-header name="Cache-Control" exists-action="override"> <value>no-store, no-cache, must-revalidate</value> </set-header> <set-header name="Pragma" exists-action="override"> <value>no-cache</value> </set-header> <!-- Remove any content-type that might interfere --> <set-header name="Content-Type" exists-action="delete" /> </return-response> </outbound> <on-error> <base /> </on-error> </policies>

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Azure-Samples/remote-mcp-apim-functions-python'

If you have feedback or need assistance with the MCP directory API, please join our Discord server