How to get the client IP in ASP.NET Core even behind a proxy

Part of implementing an error monitoring platform like elmah.io is dealing with the IP addresses of the clients generating errors. In this post, I'll show you parts of how we have implemented this in ASP.NET Core, to make sure that different hosting scenarios still produce the correct IP address.

How to get the client IP in ASP.NET Core even behind a proxy

Let's jump right in. ASP.NET Core supports getting the client IP directly on the HttpContext object available throughout various places. For Razor pages app, simply get the value of the RemoteIpAddress property and set it on the view model to see the value:

public class IndexModel : PageModel
{
    public string? Ip { get; set; }

    public void OnGet()
    {
        Ip = HttpContext.Connection.RemoteIpAddress?.ToString();
    }
}

Similarly, in ASP.NET Core MVC you can access the HttpContext from a controller:

public class HomeController : Controller
{
    public IActionResult Index()
    {
        ViewBag.Ip = HttpContext.Connection.RemoteIpAddress?.ToString();
        return View();
    }
}

Include a reference to the Ip string either through the Ip property in IndexModel or ViewBag (depending on which of the above examples you are using):

<h1 class="display-4">@Model.Ip</h1>

And launch the project to inspect the value:

IP value on UI

Notice how the value is ::1 when running the code locally. The value is a compressed version of  the IPV6 loopback address 0:0:0:0:0:0:0:1 equivalent of the IPV4 address 127.0.0.1. When deploying this to a web server somewhere, the IP will change to the real IP of the client.

Before we start digging into scenarios where the client IP cannot be resolved, let's look at other ways of getting hold of the IP. Not all classes have access to the HttpContext object as illustrated in the examples above. ASP.NET Core provides a nice little helper named IHttpContextAccessor. It is automatically registered as a dependency in all newer versions of ASP.NET Core, while you need to register it manually in old versions. Set up constructor injection of this interface:

public class MyService
{
    private readonly IHttpContextAccessor httpContextAccessor;

    public MyService(IHttpContextAccessor httpContextAccessor)
    {
        this.httpContextAccessor = httpContextAccessor;
    }

    public string? GetClientIp()
    {
        return httpContextAccessor.HttpContext?.Connection.RemoteIpAddress?.ToString();
    }
}

Notice how the code for getting the remote IP address is similar to the examples before. The only difference here is that we use the IHttpContextAccessor to get the current HttpContext. We need a null check on the HttpContext property in case the invocation is not involved in a current HTTP request.

If the httpContextAccessor object is null, make sure to register it in Program.cs or Startup.cs:

builder.Services.AddHttpContextAccessor();

In most cases, websites are hosted on a web server somewhere. Sometimes, deployments involve various technologies like reverse proxies and/or cloud hosting. You may have Cloudflare, nginx, or similar products configured to run in front of your website. Some of the products go to some lengths of either hiding the original client IP or overwriting the client IP in the original request with the IP of the proxy.

Let's look at an example where a website runs on a web server somewhere with Cloudflare configured. Cloudflare can proxy domains and sub-domains as shown here:

Proxied subdomains

This basically means that when requesting that domain, Cloudflare can execute code before forwarding the request to the proxied domain. In this example, running the code above would change the value of the RemoteIpAddress property to now includes Cloudflare's IP address and not the original client initiating the request.

Luckily, most reverse proxies support some kind of feature to get the original IP. This is typically done by including custom headers to the request. The header mainly used to include this piece of information is named X-Forwarded-For. This header is the de-facto standard that everyone adopts or at least should adopt. Let's look at how to get the original IP address through this header from ASP.NET Core MVC:

public IActionResult Index()
{
   ViewBag.Ip = Request.Headers["X-Forwarded-For"].ToString();
   return View();
}

The code fetches the value of the X-Forwarded-For header through the current request. The IHttpContextAccessor interface can be in a similar way:

httpContextAccessor.HttpContext?.Request.Headers["X-Forwarded-For"].ToString()

As you may already know, the Headers property is a dictionary of header names and values. In the example above, I simply use ToString to get the entire value of the X-Forwarded-For header. If you inspect the class returned by the indexer, it is of type StringValues. That's right, headers can have multiple values. In the case of the X-Forwarded-For header, the value can be one or more IP addresses depending on the request chain involved in serving this request. If there are multiple proxies between the client initiating the request and your server, all of the proxies supporting this header will include their IP address in the list. If you want the IP of the initiating client, this should be available as the first value. Be aware that bad proxies may try to spoof IP addresses.

This manual approach can be fine if you always want to read the header value. In case your code or some third-party library needs the RemoveIpAddress set, you can use the forwarded headers middleware built into the Microsoft.AspNetCore.HttpOverrides NuGet package by including the following in your Program.cs or Startup.cs file:

app.UseForwardedHeaders();

This will detect any value in the X-Forwarded-For header and update the request accordingly. As already mentioned, some products have settled on using other headers like the X-Coming-From value instead. You can either check for this header name manually or you can configure the forwarded headers middleware to look for alternative header names by providing it with a custom configuration:

builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedForHeaderName = "X-Coming-From";
});

Again, be aware that IP addresses can be easily spoofed. It doesn't take more than one misbehaving or misconfigured proxy between the client and your server to fill in spoofed IP addresses. When using the ForwaredHeadersMiddleware provided by Microsoft, a property named ForwardLimit is set to 1 on the options as a default. This is done to only include one IP address in the header. The ForwardedHeadersOptions class provides multiple options for controller the IP addresses to add, known proxies, and more.

That's it. There are many ways to get hold of the user's IP address. It all depends on how your website is deployed. Before I let you go, I want to put a few words on storing IP addresses in general. IP addresses are classified as personal information in some countries. If an IP address is linked to a user's activity, it can reveal their online behavior and in that way identify them. It is important to be aware of how IP addresses are stored and used, especially in situations where they may be linked to identifiable individuals. You should always provide information to your users about how their IP addresses are being collected and used, and obtain their consent where necessary. You should also make sure to implement data retention to make sure IP addresses are not stored longer than needed.

elmah.io: Error logging and Uptime Monitoring for your web apps

This blog post is brought to you by elmah.io. elmah.io is error logging, uptime monitoring, deployment tracking, and service heartbeats for your .NET and JavaScript applications. Stop relying on your users to notify you when something is wrong or dig through hundreds of megabytes of log files spread across servers. With elmah.io, we store all of your log messages, notify you through popular channels like email, Slack, and Microsoft Teams, and help you fix errors fast.

See how we can help you monitor your website for crashes Monitor your website