Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Blazor Adding HttpClientHandler to add Jwt to HTTP Header on requests

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?

like image 486
Matthew Flynn Avatar asked Aug 22 '19 11:08

Matthew Flynn


3 Answers

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")));
    }
    // ...
}
like image 93
Obliterator Avatar answered Nov 11 '22 19:11

Obliterator


@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...

like image 34
enet Avatar answered Nov 11 '22 21:11

enet


The following adds X-CSRF-TOKEN header to http requests:

public class CustomHttpMessageHandler : DelegatingHandler
{
    private readonly IJSRuntime _js;
    public CustomHttpMessageHandler(IJSRuntime js)
    {
        _js = js;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var afrt = await _js.InvokeAsync<string>("getCookie", ".AFRT");
        request.Headers.Add("X-CSRF-TOKEN", afrt);
        return await base.SendAsync(request, cancellationToken);
    }
}

In Program.cs configure like below:

builder.Services.AddScoped<CustomHttpMessageHandler>();
builder.Services.AddHttpClient("ApiClient", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
    .AddHttpMessageHandler<CustomHttpMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("ApiClient"));

You need to install Microsoft.Extensions.Http package to your blazor webassembly client.

like image 22
synergetic Avatar answered Nov 11 '22 21:11

synergetic