Log Client IP in ASP.NET Core 3.1

Jason Ge
5 min readMay 12, 2021

In old asp.net, we used to use following code to get the client IP address:

var clientIp = HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"];

In asp.net core 3.1, we can use following code to get the client IP address:

private string GetClientIP(IHttpContextAccessor contextAccessor)
{
return contextAccessor.HttpContext.Connection.RemoteIpAddress.ToString();
}

However, in the situation there is a reverse proxy or load balancer before the server, using above code would only get the IP address of the reverse proxy or load balancer.

To solve this issue, people invented the HTTP header X-Forwarded-For. The X-Forwarded-For (XFF) header is a de-facto standard header for identifying the originating IP address of a client connecting to a web server through an HTTP proxy or a load balancer. The header is a list of comma separated IP addresses:

X-Forwarded-For: <client>, <proxy1>, <proxy2>

When a proxy receives a request, it would add the remote IP address that make the request to the end of the header.

With this header, a better approach to get client IP address would be looking into this header and grab the left most IP address as the client IP address.

However, blindly trust the X-Forwarded-For header would also be problematic. Hackers can easily manipulate this header to provide false client IP address. There is no perfect solution for this.

ForwardedHeadersMiddleware in asp.net core 3.1

The Forwarded Headers Middleware (ForwardedHeadersMiddleware) in asp.net core 3.1 reads X-Forwarded-For, X-Forwarded-Proto and X-Forwarded-Host headers and fills in the associated fields on HttpContext.

The middleware updates:

  • HttpContext.Connection.RemoteIpAddress: Set using the X-Forwarded-For header value.
  • HttpContext.Request.Scheme: Set using the X-Forwarded-Proto header value.
  • HttpContext.Request.Host: Set using the X-Forwarded-Host header value.

We will focus on the work flow how the middleware process the X-Forwarded-For header. There are some configuration options that would affect the work flow:

  • ForwardLimit: Limits the number of entries in the headers that are processed. Set to null to disable the limit, but this should only be done if KnownProxies or KnownNetworks are configured.
  • KnownNetworks: Address ranges of known networks to accept forwarded headers from.
  • KnownProxies: Addresses of known proxies to accept forwarded headers from. Use KnownProxies to specify exact IP address matches.

The middleware processes the header value in reverse order from right to left. But before that, it would first check the HttpContext.Connection.RemoteIpAddress field against the KnownProxies and KnownNetworks. If the IP address is not in the list of KnownProxies and KnownNetworks, the middleware will assume the IP address is the client IP address. The X-Forwarded-For header would not be processed. This situation would most likely to happen if there is no proxy/load balancer before the server and client communicate directly to the server.

If the HttpContext.Connection.RemoteIpAddress field is inside KnownProxies or KnownNetworks, the middleware would grab the right most entry from the X-Forwarded-For header and check against the KnownProxies and KnownNetworks. If the IP address is not in the list of KnownProxies and KnownNetworks, the middleware will assume the IP address is the client IP address. Therefore, the middleware will assign the IP address to HttpContext.Connection.RemoteIpAddress field and assign the original value of HttpContext.Connection.RemoteIpAddress field to X-Original-For header. All the values that are processed will be removed from X-Forwarded-For header. If the X-Forwarded-For header is empty, it would be removed from the Headers collection; otherwise, the X-Forwarded-For header stay in Headers collection with the remaining values. If the IP address is in the list of KnownProxies and KnownNetworks, the middleware will move on to the next IP address on the X-Forwarded-For header until either the ForwardLimit is reached or all the values X-Forwarded-For header has been processed. In either case, the last one being processed by the middleware will be the client IP address.

This workflow is not very easy to understand. We will demonstrate it using several examples:

Example 1 (Most common):

  • ForwardLimit: null. There is no limit on how many entries the middleware would process in the X-Forwarded-For header.
  • KnownProxies: [ 10.130.10.77 ]. Assume this is the reverse proxy/load balancer IP address before your web application server.
  • Client connects to your web application via your load balancer. There are no other proxies involved except yours (10.130.10.77). Client IP address is 192.168.98.99.

When client sends the request to your web application, it first reaches to the load balancer. Load balancer would create the X-Forwarded-For header and add the value of 192.168.98.99. Load balancer then forwards the request to web server. When web server receives the request, before the execution of the Forwarded Headers Middleware, the HttpContext.Connection.RemoteIpAddress field would be 10.130.10.77 and the X-Forwarded-For header would be 192.168.98.99. Since 10.130.10.77 is in the list of KnownProxies while 192.168.98.99 not, after execution of the Forwarded Headers Middleware, the HttpContext.Connection.RemoteIpAddress field would be 192.168.98.99.

Before the execution:

HttpContext.Connection.RemoteIpAddress: 10.130.10.77
X-Forwarded-For: 192.168.98.99

After the execution:

HttpContext.Connection.RemoteIpAddress: 192.168.98.99
X-Original-For: 10.130.10.77
X-Forwarded-For is removed

Example 2

  • ForwardLimit: null. There is no limit on how many entries the middleware would process in the X-Forwarded-For header.
  • KnownProxies: [ 10.130.10.77, 100.13.10.60 ].
  • Client goes through multiple proxies to connect to your web application . Client IP address is 192.168.98.99.

When client sends the request to your web application, it first reaches to the Proxy 1. Proxy 1 would create the X-Forwarded-For header and add the value of 192.168.98.99. When the request reaches to Proxy 2, Proxy 2 would append Proxy 1’s IP address to the X-Forwarded-For header. When the request reach to the load balancer, load balancer would append the Proxy 2’s IP address to the X-Forwarded-For header and then forward the request to web server. When web server receives the request, before the execution of the Forwarded Headers Middleware, the HttpContext.Connection.RemoteIpAddress field would be 10.130.10.77 and the X-Forwarded-For header would be “192.168.98.99, 200.13.10.50, 100.13.10.60". Since both 10.130.10.77 and 100.13.10.60 are in the list of KnownProxies while 200.13.10.50 not, after execution of the Forwarded Headers Middleware, the HttpContext.Connection.RemoteIpAddress field would be 200.13.10.50.

Before the execution:

HttpContext.Connection.RemoteIpAddress: 10.130.10.77
X-Forwarded-For: 192.168.98.99, 200.13.10.50, 100.13.10.60

After the execution:

HttpContext.Connection.RemoteIpAddress: 200.13.10.50
X-Original-For: 10.130.10.77
X-Forwarded-For: 192.168.98.99

In this case, you can see the IP address we have finally grabbed as client IP address is actually the Proxy 2’s IP address. This is by design behavior since we do not trust Proxy 1. Information sent out by Proxy 1 may not be trusted and thus should not be used to determine the original IP address. If we are sure Proxy 1 is a legitimate proxy and should be trusted, we can add its IP address to KnownProxies list and the middleware would continue on to the next IP address in the header.

You can use the Forwarded Headers Middleware in asp.net core 3.1 using following code snippet in Startup.cs file.

--

--

Jason Ge

Software developer with over 20 years experience. Recently focus on Vue/Angular and asp.net core.