Skip to content
This repository has been archived by the owner on Nov 19, 2024. It is now read-only.

Blazor Server

Dominick Baier edited this page Aug 4, 2022 · 10 revisions

Overview

Blazor Server applications have the same token management requirements like a regular ASP.NET Core web application. Due to the fact that Blazor puts an abstraction layer between the Blazor application and the hosting/rendering engine, you cannot use HttpContext.

This means

  • you cannot use the HttpContext extension methods
  • you cannot use the ASP.NET authentication session to store tokens
  • you cannot use HTTP message handlers to automatically attach tokens to API calls

This document describes some workarounds. Also see the BlazorServer sample for the full source code.

Token storage

Since the tokens cannot be managed in the authentication session, you need to provide an alternative store like in-memory or a database. The OpenID Connect provides event from where you can get the initial tokens after authentication:

public class OidcEvents : OpenIdConnectEvents
{
    private readonly IUserTokenStore _store;

    public OidcEvents(IUserTokenStore store)
    {
        _store = store;
    }
    
    public override async Task TokenValidated(TokenValidatedContext context)
    {
        var exp = DateTimeOffset.UtcNow.AddSeconds(Double.Parse(context.TokenEndpointResponse!.ExpiresIn));

        await _store.StoreTokenAsync(context.Principal!, new UserToken
        {
            AccessToken = context.TokenEndpointResponse.AccessToken,
            Expiration = exp,
            RefreshToken = context.TokenEndpointResponse.RefreshToken,
            Scope = context.TokenEndpointResponse.Scope
        });
        
        await base.TokenValidated(context);
    }
}

Retrieving and using tokens

Blazor has its own abstraction to access the current user, it's called the AuthenticationStateProvider.

The following is a sample API client that retrieves the token for the current user from the token management service and calls an API:

public class RemoteApiService
{
    private readonly HttpClient _client;
    private readonly AuthenticationStateProvider _authenticationStateProvider;
    private readonly IUserTokenManagementService _tokenManagementService;

    public RemoteApiService(
        HttpClient client,
        AuthenticationStateProvider authenticationStateProvider,
        IUserTokenManagementService tokenManagementService)
    {
        _client = client;
        _authenticationStateProvider = authenticationStateProvider;
        _tokenManagementService = tokenManagementService;
    }

    public async Task<Data> GetData()
    {
        var request = new HttpRequestMessage(HttpMethod.Get, "endpoint");
        var response = await SendRequestAsync(request);

        // rest omitted
    }

    private async Task<HttpResponseMessage> SendRequestAsync(HttpRequestMessage request)
    {
        var state = await _authenticationStateProvider.GetAuthenticationStateAsync();
        var token = await _tokenManagementService.GetAccessTokenAsync(state.User);

        request.SetBearerToken(token.AccessToken);
        return  await _client.SendAsync(request);
    }
}

This wrapper could then be registered with the DI system, e.g.:

builder.Services.AddTransient<RemoteApiService>();
builder.Services.AddHttpClient<RemoteApiService>(client =>
{
    client.BaseAddress = new Uri("https://api.company.com/invoices/");
});