How to securely reverse-proxy ASP.NET Core

By FoxLearn 3/7/2025 9:37:13 AM   74
ASP.NET Core applications are powered by Kestrel, a fast, solid, and reliable web server. While Kestrel is capable of serving as a front-facing server, it's rarely exposed directly to the internet.

Instead, most developers use other web servers, such as Nginx, Traefik, and Caddy, to act as a reverse proxy in front of Kestrel for several key reasons:

  1. TLS Termination: The reverse proxy handles TLS encryption and decryption (HTTPS).
  2. Domain and Port Handling: It manages requests on specific domains and ports, often via virtual host configurations.
  3. Security Features: Reverse proxies enable rate limiting, request filtering, HSTS, etc.
  4. Performance Optimizations: They offer features like request compression and caching based on browser capabilities.
  5. Load Balancing and Failover: Reverse proxies manage load balancing, failover support, and response transformations.

While Kestrel can handle many of these tasks, managing them across multiple apps is simpler with a single reverse proxy configuration.

The Importance of Header Forwarding

When using a reverse proxy with ASP.NET Core applications, certain original HTTP request details like the client’s IP address, domain, and port are often lost. However, these details are crucial for the proper functioning of your app.

This is where header forwarding comes into play. The reverse proxy forwards this important information to the backend server using special HTTP headers. The key headers used for this purpose are:

  • X-Forwarded-For: The original client IP address.
  • X-Forwarded-Host: The host requested by the client.
  • X-Forwarded-Proto: The protocol used by the client (HTTP or HTTPS).
  • X-Forwarded-Prefix: The path base used by the client.

These headers are essential, especially for scenarios where ASP.NET Core needs to generate URLs (such as in Razor templates or OAuth2 redirects).

From a security perspective, X-Forwarded-For is critical. Without it, your app cannot access the original client’s IP address, which can affect logging, authentication, authorization, and rate limiting.

Securely Interpreting X-Forwarded-* Headers in ASP.NET Core

Properly handling X-Forwarded- headers* is crucial to maintaining security. Attackers might attempt to manipulate these headers to bypass your security measures, so it’s important to ensure they come from a trusted source.

To do this, your reverse proxy must strip these headers from client requests. By default, reverse proxies (when properly configured) won’t accept untrusted header values. Instead, they populate these headers using reliable values like the TCP connection and the original HTTP headers (e.g., Host).

To avoid writing custom code to handle these headers, ASP.NET Core provides built-in middleware for securely processing them:

// [...]
// Add this line before other middleware
app.UseForwardedHeaders();
app.UseAuthentication();
app.UseAuthorization();
// [...]

The UseForwardedHeaders middleware must be placed before any other middleware, particularly before UseHsts. It can, however, follow middleware for diagnostics and error handling.

Ensure the middleware is configured to forward the correct headers, otherwise it will remain inactive.

For example, to forward the X-Forwarded-For, X-Forwarded-Host, X-Forwarded-Proto, and X-Forwarded-Prefix headers, use this configuration:

builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders =
        ForwardedHeaders.XForwardedFor | 
        ForwardedHeaders.XForwardedProto | 
        ForwardedHeaders.XForwardedHost | 
        ForwardedHeaders.XForwardedPrefix;

    // Alternatively, use:
    // options.ForwardedHeaders = ForwardedHeaders.All;
});

The ForwardedHeadersOptions are highly configurable, allowing you to change the header names if they differ from the standard X-Forwarded-*.

The ForwardedHeadersMiddleware, configured via UseForwardedHeaders, will automatically update HTTP request properties like RemoteIpAddress, Host, Scheme, and PathBase.

Restricting Reverse Proxy IP Addresses for Header Forwarding

If you know the IP addresses or ranges of trusted reverse proxies, you can restrict header forwarding to those sources only.

For example, to trust headers from a specific reverse proxy with IP 10.0.0.100 or from a network range 10.0.0.0/24, configure ForwardedHeadersOptions like this:

builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    // Trust only the IP address 10.0.0.100 for header forwarding
    options.KnownProxies.Add(IPAddress.Parse("10.0.0.100"));

    // Or trust all IPs within the 10.0.0.0/24 network range
    options.KnownNetworks.Add(IPNetwork.Parse("10.0.0.0/24"));
});

This ensures that only headers from trusted sources are accepted.

Enabling Forwarded Headers Without Code Changes

ASP.NET Core also provides a simple way to enable header forwarding without needing to modify your application's code. You can set the environment variable ASPNETCORE_FORWARDEDHEADERS_ENABLED to true. This flag enables forwarding headers for the original IP and protocol but does not support advanced configurations like restricting IPs via KnownNetworks.

By correctly configuring reverse proxies and forwarding headers in ASP.NET Core, you can ensure your app handles requests securely and efficiently, all while preserving important client information like IP addresses and protocol details.