How do I get client IP and browser info in Blazor?

In aspnetcore3.1 this works for me:

  1. Make special class for holding required info:
public class ConnectionInfo
{
    public string RemoteIpAddress { get; set; } = "-none-";
}
  1. Create instance in _Host.cshtml and pass to App component as parameter:
@{
    var connectionInfo = new ConnectionInfo() 
    { 
        RemoteIpAddress = Request.HttpContext.Connection.RemoteIpAddress.ToString() 
    };
}
...
<component type="typeof(App)" 
           render-mode="ServerPrerendered"
           param-ConnectionInfo="connectionInfo" />
  1. In App.razor catch and re-publish as CascadingValue:
<CascadingValue Value="connectionInfo">
  <Router AppAssembly="typeof(Program).Assembly">
      ...
  </Router>
</CascadingValue>

@code {
    [Parameter]
    public ConnectionInfo? connectionInfo { get; set; }
}
  1. Obtain in any child page/component as CascadingParameter:
@code {
    [CascadingParameter]
    private ConnectionInfo? connectionInfo { get; set; }
}

The only problem here is with roaming users - when user changes his IP address and Blazor does not "catch" this (for example browser tab in background) you will have old IP address until user refreshes (F5) page.


Note that this is only referring to server-side Blazor.

"There is no a good way to do this at the moment. We will look into how we can provide make this information available to the client."

Source: Blazor dev at Github

Workaround

The client makes an ajax call to the server, which then can pick up the local ip number. Javascript:

window.GetIP = function () {
    var token = $('input[name="__RequestVerificationToken"]').val();
    var myData = {}; //if you want to post extra data
    var dataWithAntiforgeryToken = $.extend(myData, { '__RequestVerificationToken': token });
    var ip = String('');
    $.ajax({
        async: !1, //async works as well 
        url: "/api/sampledata/getip",
        type: "POST",
        data: dataWithAntiforgeryToken,
        success: function (data) {
            ip = data;
            console.log('Got IP: ' + ip);
        },
        error: function () {
            console.log('Failed to get IP!');
        }
    });
    return ip;
};

Backend (ASP.NET Core 3.0):

    [HttpPost("[action]")]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public string GetIP()
    {
        return HttpContext.Connection.RemoteIpAddress?.ToString();
    }

Note that this is not secure, the ipnumber can be spoofed so don't use for anything important.


Here's how to do it in server side Blazor for .NET 5 in 2021.

Please note that my solution will only provide you an IP address, but getting an user agent should be easy using my solution.

I will just copy the contents of my blog post available here: https://bartecki.me/blog/Blazor-serverside-get-remote-ip

TLDR:

You can use JavaScript to call your own exposed endpoint that will return remote connection IP using following code:

RemoteIpAddress = Request.HttpContext.Connection.RemoteIpAddress.ToString() 

... with the downside of having to handle your reverse proxy server if you have any, otherwise you will just get your reverse proxy's IP address.

or you can call an external endpoint using JavaScript that will return an IP address for you with the downside that you will have to configure CORS and even then it can be blocked by some adblocking extensions.

Details:

Two approaches

Approach 1: Call an external service using JavaScript

Pros:

  • Slightly more simpler if you are using reverse proxy like nginx, traefik

Cons:

  • May be blocked by external extensions/adblockers
  • You will have to configure CORS
_Host.cshtml
<script>
  window.getIpAddress = () => {
    return fetch('https://jsonip.com/')
      .then((response) => response.json())
      .then((data) => {
        return data.ip
      })
  }
</script>
RazorPage.razor.cs
    public partial class RazorPage : ComponentBase
    {
        [Inject] public IJSRuntime jsRuntime { get; set; }

        public async Task<string> GetIpAddress()
        {
            try
            {
                var ipAddress = await jsRuntime.InvokeAsync<string>("getIpAddress")
                    .ConfigureAwait(true);
                return ipAddress;
            }
            catch(Exception e)
            {
                //If your request was blocked by CORS or some extension like uBlock Origin then you will get an exception.
                return string.Empty;
            }
        }
    }
Startup.cs
        public void ConfigureServices(IServiceCollection services)
        {
            //code...
            services
                .AddCors(x => x.AddPolicy("externalRequests",
                    policy => policy
                .WithOrigins("https://jsonip.com")));
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            //code...
            app.UseCors("externalRequests");
        }

Approach 2: Expose an endpoint in our Blazor app and call it using JavaScript

Pros:

  • You won't have to configure CORS
  • Won't be blocked by extensions or adblockers

Cons:

  • May be slightly more complicated if you are using a reverse proxy like nginx, traefik, etc.

Now take care as you will use this approach, because if you are using a reverse proxy, then you will actually receive your reverse proxy IP address. It is very possible that your reverse proxy is already forwarding an IP address of the external client in some sort of header, but it is up to you to figure it out.

Example: https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/

InfoController.cs
    [Route("api/[controller]")]
    [ApiController]
    public class InfoController : ControllerBase
    {
        [HttpGet]
        [Route("ipaddress")]
        public async Task<string> GetIpAddress()
        {
            var remoteIpAddress = this.HttpContext.Request.HttpContext.Connection.RemoteIpAddress;
            if (remoteIpAddress != null)
                return remoteIpAddress.ToString();
            return string.Empty;
        }
    }
Startup.cs
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers(); //remember to map controllers if you don't have this line
                endpoints.MapBlazorHub();
                endpoints.MapFallbackToPage("/_Host");
            });
_Host.cshtml
<script>
  window.getIpAddress = () => {
    return fetch('/api/info/ipaddress')
      .then((response) => response.text())
      .then((data) => {
        return data
      })
  }
</script>
RazorPage.razor.cs
    public partial class RazorPage : ComponentBase
    {
        [Inject] public IJSRuntime jsRuntime { get; set; }

        public async Task<string> GetIpAddress()
        {
            try
            {
                var ipAddress = await jsRuntime.InvokeAsync<string>("getIpAddress")
                    .ConfigureAwait(true);
                return ipAddress;
            }
            catch(Exception e)
            {
                //If your request was blocked by CORS or some extension like uBlock Origin then you will get an exception.
                return string.Empty;
            }
        }
    }

Well, I came across this issue this morning and the way I solved it for server-side Blazor was to create a class that you can then inject as a scoped service on your _host.cshtml, and then access it anywhere on your Blazor components, as Razor pages already have support for this.

    public class BlazorAppContext
    {
        /// <summary>
        /// The IP for the current session
        /// </summary>
        public string CurrentUserIP { get; set; }
    }

Startup.cs:

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
      ...

      services.AddScoped<BlazorAppContext>();

      ...
    }

_host.cshtml:

@inject IHttpContextAccessor httpContextAccessor
@{
    BlazorAppContext.CurrentUserIP =   httpContextAccessor.HttpContext.Connection?.RemoteIpAddress.ToString();
}

You can also try a Scoped approach that you can then use through DI.

Annotation:

As stated in the documentation, "Blazor WebAssembly apps don't currently have a concept of DI scopes. Scoped-registered services behave like Singleton services. However, the Blazor Server hosting model supports the Scoped lifetime. In Blazor Server apps, a scoped service registration is scoped to the connection. For this reason, using scoped services is preferred for services that should be scoped to the current user, even if the current intent is to run the client-side in the browser."

I hope it helps.