Table of Contents

    Book an Appointment

    INTRODUCTION

    While working on an enterprise SaaS platform migration, our engineering team was tasked with moving an ASP.NET Core 8 Razor Pages application into a Dockerized environment behind an AWS API Gateway. The architecture was designed for high availability, but immediately after deploying to the staging environment, we encountered a situation where the application became entirely unusable.

    During our initial tests, the application worked flawlessly when accessed directly via the internal EC2 network. However, the moment traffic was routed through the AWS API Gateway, the authentication flow collapsed. Users attempting to log in were met with immediate HTTP 400 Bad Request errors on POST actions. Furthermore, automatic redirects—such as bouncing unauthenticated users from the dashboard to the login page—were failing entirely or sending users to bizarre internal URLs.

    Reverse proxy misconfigurations are a common pitfall in cloud deployments, often leading to silent security failures, dropped cookies, or blocked requests. This challenge inspired this article, detailing how we uncovered the root cause of these proxy issues and properly configured ASP.NET Core 8 to trust and process AWS API Gateway traffic securely. Sharing this experience ensures that other engineering teams can avoid spending days debugging opaque proxy routing failures.

    PROBLEM CONTEXT

    The application is a centralized access management platform built with ASP.NET Core 8 Razor Pages. It runs inside Docker containers, orchestrated to scale horizontally. To manage external traffic, enforce throttling, and handle SSL termination, we placed an AWS API Gateway in front of the application network.

    In this architecture, the API Gateway receives HTTPS requests from the public internet, terminates the SSL connection, and forwards the requests to the backend Docker containers over HTTP. The backend application relies heavily on cookie-based authentication, anti-forgery tokens (CSRF protection) for state-changing POST requests, and identity middleware for session management.

    When bypassing the Gateway, everything functioned perfectly. But behind the Gateway, the architecture’s assumptions about network protocols, hostnames, and ports began to unravel, exposing deep flaws in how the application interpreted incoming request metadata.

    WHAT WENT WRONG

    The symptoms were confusing and seemingly disconnected. When users navigated to the application, they received the initial page, but submitting the login form resulted in a hard HTTP 400 error before the request even reached the Razor Page handler. Additionally, any middleware-triggered redirect (like the identity challenge redirecting to the login path) completely failed.

    We dove into the container logs and found several critical warning signs:

    [WRN] Failed to determine the https port for redirect. 
    [INF] AuthenticationScheme: Cookies was challenged.
    [INF] [DEBUG] Scheme: http, URL: http://internal-aws-url:8085/authentication/login

    Dumping the HttpContext revealed the core of the issue. The application registered the request scheme as http instead of https. Worse, the host URL was not the public DNS name; it was an internal AWS DNS name appended with port 8085. Neither the host nor the port matched the public-facing URL or the container’s internal listener.

    This mismatch triggered a cascading failure:

    • Broken Redirects: Because ASP.NET Core believed it was running on HTTP at an internal AWS domain, the authentication middleware generated redirect URIs pointing to that internal endpoint. The browser, unable to resolve the internal domain, simply failed to load.
    • HTTP 400 on POST (Anti-Forgery Failure): ASP.NET Core’s built-in anti-forgery validation strictly verifies the origin, scheme, and host of the request. Since the browser was sending cookies over HTTPS for the public domain, but Kestrel (the internal web server) saw an incoming HTTP request for an internal domain, the anti-forgery token validation failed instantly, returning a 400 Bad Request.

    HOW WE APPROACHED THE SOLUTION

    Our initial suspect was the Forwarded Headers middleware. By default, proxies like AWS API Gateway append X-Forwarded-For, X-Forwarded-Proto, and X-Forwarded-Host headers to inform the backend about the original request.

    The existing configuration looked like this:

    builder.Services.Configure<ForwardedHeadersOptions>(options =>
    {
        options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost;
        options.KnownNetworks.Clear();
        options.KnownProxies.Clear();
    });
    app.UseForwardedHeaders();

    While clearing the known networks forces the application to accept forwarded headers from any source, the middleware only works if the Gateway actually sends those exact headers in the expected format. We discovered that the AWS API Gateway configuration was either stripping the host header or injecting its own integration endpoint host.

    Additionally, the cookie policies were set to CookieSecurePolicy.SameAsRequest. Because Kestrel was receiving the request over HTTP (after SSL termination), it was instructing the browser to treat authentication cookies as insecure, further compounding the domain and scheme mismatch.

    We needed a two-pronged approach: align the Gateway’s header mappings with standard proxy conventions, and harden the ASP.NET Core middleware to explicitly override the scheme and enforce secure cookie policies regardless of the internal termination protocol.

    FINAL IMPLEMENTATION

    To resolve the issue permanently without requiring extensive network-level changes on the client’s AWS infrastructure, we adjusted the ASP.NET Core configuration to force HTTPS context and properly parse the complex proxy headers.

    1. Enforcing Forwarded Headers and Scheme

    We updated the Forwarded Headers configuration to gracefully handle scenarios where the proxy chain might be longer or more complex than expected. We also added a fallback middleware to ensure the scheme is always set to HTTPS when running in production, preventing redirect generation failures.

    // Configure Forwarded Headers
    builder.Services.Configure<ForwardedHeadersOptions>(options =>
    {
        options.ForwardedHeaders = ForwardedHeaders.All;
        
        // In a zero-trust or heavily proxied environment where the Gateway IPs change
        options.KnownNetworks.Clear();
        options.KnownProxies.Clear();
    });
    // Later in the pipeline, ensure it's registered FIRST
    app.UseForwardedHeaders();
    // Force HTTPS scheme if X-Forwarded-Proto is missing but we know it's behind a secure gateway
    app.Use((context, next) =>
    {
        if (context.Request.Headers.TryGetValue("X-Forwarded-Proto", out var proto) && proto == "https")
        {
            context.Request.Scheme = "https";
        }
        else if (app.Environment.IsProduction())
        {
            // Fallback for strict production environments behind SSL-terminating gateways
            context.Request.Scheme = "https";
        }
        return next();
    });
    

    2. Securing Cookie Policies

    We changed the cookie configuration to mandate strict security. By changing CookieSecurePolicy.SameAsRequest to CookieSecurePolicy.Always, we ensured that even if the Forwarded Headers middleware temporarily failed to map the protocol, the application would never issue insecure session cookies.

    builder.Services.Configure<CookiePolicyOptions>(options =>
    {
        options.CheckConsentNeeded = context => false;
        options.MinimumSameSitePolicy = SameSiteMode.Lax;
        options.HttpOnly = HttpOnlyPolicy.Always;
        // Force secure cookies since public access is entirely HTTPS
        options.Secure = CookieSecurePolicy.Always; 
    });
    builder.Services.AddSession(options =>
    {
        options.IdleTimeout = TimeSpan.FromHours(24);
        options.Cookie.HttpOnly = true;
        options.Cookie.IsEssential = true;
        options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
    });
    

    3. Anti-Forgery Configuration

    Finally, we explicitly configured the Anti-Forgery system to suppress X-Frame-Options headers if handled elsewhere, and to ensure the cookie it generates is also strictly secure.

    builder.Services.AddAntiforgery(options => 
    {
        options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
    });

    Once deployed, these changes allowed Kestrel to accurately reconstruct the public URL context. The Anti-Forgery token validation succeeded, POST requests were processed correctly, and all identity redirects successfully routed users to the public domain.

    LESSONS FOR ENGINEERING TEAMS

    Deploying web applications behind cloud-native API gateways requires a deep understanding of HTTP semantics. If you plan to hire software developer teams for cloud migrations, ensure they understand reverse proxy architectures. Here are our key takeaways:

    • Middleware Order is Critical: app.UseForwardedHeaders() must be placed at the very top of your middleware pipeline. If it executes after authentication or routing, the application will process the request using the internal HTTP context.
    • Never Trust SameAsRequest Behind a Proxy: When SSL terminates at the load balancer or gateway, setting cookie policies to SameAsRequest is a trap. Always use CookieSecurePolicy.Always for production environments.
    • Anti-Forgery Token strictness: Unexplained HTTP 400 errors on POST requests in ASP.NET Core are almost always Anti-Forgery token validation failures. Check your request scheme and host mappings first.
    • API Gateway Header Mapping: Cloud providers often overwrite headers. AWS API Gateway, for instance, might require specific Mapping Templates to pass X-Forwarded-Host properly to the backend container.
    • Visibility is Everything: Dumping the HttpContext.Request.Scheme and Host was the breakthrough in this debugging session. Always build introspection endpoints when troubleshooting proxy issues.

    When organizations look to hire dotnet developers for enterprise modernization, the ability to trace network layer issues down to application-level security policies is an invaluable skill.

    WRAP UP

    Reverse proxies and API gateways are powerful architectural components, but they obscure the true origin of network requests from the backend application. By meticulously configuring forwarded headers and locking down cookie security policies, we restored functionality to the ASP.NET Core 8 SaaS platform without compromising security.

    Complex cloud deployments require experienced engineering oversight to ensure seamless transitions from direct-access environments to sophisticated gateway architectures. If your team is struggling with backend modernization, cloud migrations, or architectural bottlenecks, contact us to learn how our pre-vetted engineers can accelerate your roadmap.

    Social Hashtags

    #DotNet #ASPNETCore #ASPNETCore8 #AWS #APIGateway #CloudComputing #Docker #DevOps #WebDevelopment #SoftwareEngineering #CloudMigration #CyberSecurity #RazorPages #Microservices #BackendDevelopment

     

     

    Frequently Asked Questions

    Success Stories That Inspire

    See how our team takes complex business challenges and turns them into powerful, scalable digital solutions. From custom software and web applications to automation, integrations, and cloud-ready systems, each project reflects our commitment to innovation, performance, and long-term value.