Blazor Adding HttpClientHandler to add Jwt to HTTP Header on requests

11,578

Solution 1

@Matthew Flynn, currently you can't use IHttpClientFactory on client-side Blazor.

And you don't have to derive from HttpMessageHandler (DelegatingHandler). It has already been done by Blazor. The following is n extension class to extend the functionality of the HttpClient service to enable the ability to add the Jwt token to the header of the request message...

ServiceExtensions.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Security.Claims;
    using System.Text;
    using System.Text.Json.Serialization;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Components;
    using Microsoft.Extensions.DependencyInjection;

 public static class ServiceExtensions
    {    
     public static async Task<T> GetJsonAsync<T>(this HttpClient httpClient, string url, AuthenticationHeaderValue authorization)
            {
                var request = new HttpRequestMessage(HttpMethod.Get, url);
                request.Headers.Authorization = authorization;

                var response = await httpClient.SendAsync(request);
                var responseBytes = await response.Content.ReadAsByteArrayAsync();
                return JsonSerializer.Parse<T>(responseBytes, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
            }
}

The following show how to call an endpoint on your Web Api, passing the Jwt token which is read from the localStorage. (incidentally, none of these versions is secured with data protection)

Index.razor

@page "/"

@inject ILocalStorageService localStorage
@inject HttpClient Http

<div class="mdc-card main-content-card">
    <h1 class="@MdcTypography.H4">Hello, world!</h1>

    Welcome to your new app.
</div>

// Razor content to display emloyees come here.....

@code {
Employee[] employees;

    protected override async Task OnInitAsync()
    {
        var token = await localStorage.GetTokenAsync();
        employees = await Http.GetJsonAsync<Employee[]>(
            "api/employees",
            new AuthenticationHeaderValue("Bearer", token));
    }
}

Hope this works... If not, and you can't solve thr errors, come here and tell the community about it...

Solution 2

I found a really good tutorial and sample demonstrating this (complete with roles/policy based claims): https://chrissainty.com/securing-your-blazor-apps-authentication-with-clientside-blazor-using-webapi-aspnet-core-identity/

Here is an extract below, setting the default request headers on the default http client (through DI). All calls to your web api will then include the bearer token:

public class ApiAuthenticationStateProvider : AuthenticationStateProvider
{
    private readonly HttpClient _httpClient;
    private readonly ILocalStorageService _localStorage;

    public ApiAuthenticationStateProvider(HttpClient httpClient, ILocalStorageService localStorage)
    {
        _httpClient = httpClient;
        _localStorage = localStorage;
    }
    public override async Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        var savedToken = await _localStorage.GetItemAsync<string>("authToken");

        if (string.IsNullOrWhiteSpace(savedToken))
        {
            return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
        }

        // **************    Set JWT header       ****************
        _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", savedToken);
        // *******************************************************

        return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(savedToken), "jwt")));
    }
    // ...
}
Share:
11,578
Matthew Flynn
Author by

Matthew Flynn

Updated on June 11, 2022

Comments

  • Matthew Flynn
    Matthew Flynn almost 2 years

    I am using the Visual Studio 2019 and .Net Core 3.0.0-preview-7 with the standard Blazor Client, Server and Shared templates.

    In the application our server side WebApi application will always require a JWT token to be present in the header for authorization.

    From looking at the following

    Make HTTP requests using IHttpClientFactory in ASP.NET Core

    I created the following handler;

    public class JwtTokenHeaderHandler : DelegatingHandler
    {
        private readonly ILocalStorageService _localStorage;
    
        public JwtTokenHeaderHandler(ILocalStorageService localStorage)
        {
            _localStorage = localStorage;
        }
    
        protected override async Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request,
            CancellationToken cancellationToken)
        {
            if (!request.Headers.Contains("bearer"))
            {
                var savedToken = await _localStorage.GetItemAsync<string>("authToken");
    
                if (!string.IsNullOrWhiteSpace(savedToken))
                {
                    request.Headers.Add("bearer", savedToken);
                }
            }
    
            return await base.SendAsync(request, cancellationToken);
        }
    }
    

    Where I use Blazored.LocalStorage to get the saved token from localstorage and add it to the header.

    Now, at this point I am not sure what to do as if I add the following to the Blazor.Client Startup.cs;

    services.AddTransient<JwtTokenHeaderHandler>();
    services.AddHttpClient("JwtTokenHandler")
        .AddHttpMessageHandler<JwtTokenHeaderHandler>();
    

    I get the error message;

    'IServiceCollection' does not contain a definition for 'AddHttpClient' and no accessible extension method 'AddHttpClient' accepting a first argument of type 'IServiceCollection' could be found (are you missing a using directive or an assembly reference?)

    Can anyone direct me to what I am doing wrong here?