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 ifKnownProxies
orKnownNetworks
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.