The Challenge
Azure App Service is multi-tenant; it uses the Host header to route requests. Normally, if you point a domain at Azure without adding it as a "Custom Domain," Azure rejects the request (404/503). Furthermore, authentication flows (OIDC) and persistent connections (WebSockets) break because they rely on matching domains and protocols across the proxy.
The Solution
1. The Reverse Proxy (Cloudflare Worker)
Deploy a Worker to act as an "Application Gateway."
Header Swapping: The Worker intercepts requests to the public domain, changes the Host header to the Azure fqdn so Azure accepts the traffic, but preserves the original domain in X-Forwarded-Host.
Response Rewriting: Then implement logic to catch Location headers (redirects) and Set-Cookie attributes. This ensures that any internal Azure URLs are swapped back to your public domain before reaching the user's browser.
2. Microsoft Entra ID (OAuth2/OIDC) Support
Authentication often breaks behind proxies due to "Correlation failed" errors. It's fixed by:
Updating Redirect URIs: Registered the public domain callback in the Entra ID App Registration.
Cookie Domain Correction: The Worker was configured to dynamically rewrite the Domain= attribute of Azure’s affinity and session cookies to match the public domain.
Cookie Policy: In ASP.NET, force SameSite=None and Secure=Always to ensure browsers send security tokens back through the proxy.
3. SignalR & WebSocket Integration
Standard Workers block WebSockets unless explicitly handled.
Detect Upgrades: Intercept the Upgrade: websocket header.
Handshake Preservation: Use the Worker’s fetch API specifically to tunnel the handshake while maintaining the correct Host and Forwarded headers.
Infrastructure Alignment: Enable the "Web Sockets" toggle in the Azure App Service configuration.
4. ASP.NET Core Middleware Tuning
Reconfigure the .NET pipeline to "trust" the Worker:
Forwarded Headers: Use app.UseForwardedHeaders() at the very top of Program.cs. This allows the app to correctly determine that the user is on HTTPS and on the custom domain, preventing infinite redirect loops.
Middleware Order: Place UseForwardedHeaders before UseAuthentication so the login logic sees the correct request metadata.
Final Infrastructure Map
User Browser → ://{public domain} (HTTPS)
Cloudflare Worker → Rewrites Host to the azure fqdn
Azure App Service → Processes request (thinks it's local)
Cloudflare Worker → Rewrites Cookies/Redirects back to the public domain
User Browser → Seamlessly logged in and connected via WebSockets
The Code
This setup allows for a custom domain without the Azure Custom Domain requirement, while supporting Microsoft Entra ID and WebSockets.
1. Cloudflare Worker Gateway Script
This script acts as the "Application Gateway," rewriting headers to satisfy Azure’s routing while fixing cookies and redirects for the client.
javascript
};
2. ASP.NET Core Program.cs Configuration
The following dotnet config ensures the application trusts the Worker headers and correctly handles OIDC "Correlation" cookies.
csharp
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost;
// Clear known networks/proxies to allow the Cloudflare Worker IPs
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
});
builder.Services.Configure<CookiePolicyOptions>(options =>
{
// Essential for OIDC behind proxies
options.MinimumSameSitePolicy = SameSiteMode.None;
options.Secure = CookieSecurePolicy.Always;
});
authBuilder.AddMicrosoftIdentityWebApp( options =>
{
builder.Configuration.Bind("AzureAd", options);
options.Events.OnRedirectToIdentityProvider = context => {
var request = context.HttpContext.Request;
var forwardedHost = request.Headers["X-Forwarded-Host"].FirstOrDefault();
var forwardedProto = request.Headers["X-Forwarded-Proto"].FirstOrDefault();
if (!string.IsNullOrEmpty(forwardedHost) && !string.IsNullOrEmpty(forwardedProto))
{
var redirectUri = $"{forwardedProto}://{forwardedHost}{request.PathBase}/signin-oidc";
context.ProtocolMessage.RedirectUri = redirectUri;
}
// else: use default
return Task.CompletedTask;
};
})
app.UseForwardedHeaders();
3. Key Infrastructure Requirements
Azure Portal: Navigate to your App Service > Configuration > General Settings and set Web sockets to On.
Cloudflare Dashboard: Navigate to Network and ensure WebSockets is Enabled.
SSL/TLS: Cloudflare encryption mode must be set to Full or Full (Strict).
Entra ID: Add the public domain to your App Registration > Authentication > Redirect URIs.