Blazor WASM - Global Catch 401 and navigate to login page on all HttpClient calls

Viewed 1517

In blazor template httpclient is added in the Program.cs class:

builder.Services.AddTransient(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

and later used as following:

forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");

is there any way that I can override that base httpclient and inject it to all of my pages, that would:

  • catch a 401 returned in any sever request (post/get etc.)
  • try refreshing the token (call API for that)
  • return to login when it fails to refresh

I saw that I could define a custom service that would wrap all of those HTTP client calls and perform the actions that I mention, however is there a way to do it in a better way?

2 Answers
  1. Create custom DelegatingHandler with handling 401 response code.
public class CustomMessageHandler : DelegatingHandler
{
    private readonly string host;
    readonly NavigationManager _navigationManager;

    CustomMessageHandler(IWebAssemblyHostEnvironment webAssemblyHostEnvironment, navigationManager)
    {
        host = webAssemblyHostEnvironment.BaseAddress;
        _navigationManager = navigationManager;
    }

    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var response = await base.SendAsync(request, cancellationToken);

        if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
        {
            Console.WriteLine("CustomMessageHandler catch 401");

            // TODO: some logic (e.g. refreshing token)
            
            // Or just redirect to login page.
            string loingUrl = "http://some-site/login";
            _navigationManager.NavigateTo(loingUrl, forceLoad: true);
        }

        return response;
    }
}
  1. Register created CustomMessageHandler in IServiceCollection (in blazor.Client project)
builder.Services.AddScoped<CustomMessageHandler>();
  1. Also register configured httpClient (in blazor.Client project) with our CustomMessageHandler. .AddHttpClient requires Microsoft.Extensions.Http nuget package.
builder.Services.AddHttpClient("BlazorServerHttpClient", 
    client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
        .AddHttpMessageHandler<CustomMessageHandler>();
  1. All httpClient's requests from blazor.Client to blazor.Server have to use IHttpClientFactory like this:
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;

public class WeatherService
{
    private readonly IHttpClientFactory _clientFactory;

    public WeatherService(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public async Task<WeatherForecast[]> GetWeatherForecast()
    {
        var client = _clientFactory.CreateClient("BlazorServerHttpClient");
        WeatherForecast[] result = await client.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");

        return result;
    }
}

If you want to intercept all calls via HttpClient then you'll need to create a service that lets you make HttpCalls, so you can intercept them in that and consume the HttpClient.

PS: You should change the registration from Transient to Scoped for any class (such as HttpClient) that implements IDisposable. That is going to change in a future Blazor template.

Related