Call an API from a Blazor application both protected with Microsoft Identity

Enrico Rossini 186 Reputation points
2023-10-19T07:24:04.4666667+00:00

I'm building an application with Blazor Web Assembly hosted in an ASP.NET Core application. Based from scratch and the boilerplate from Visual Studio, I created the solution.

In another solution I have a web API build with ASP.NET Core (I will call them External API). This application has its own URL and registration in the App Registration in the Azure portal. From Swagger, I can access the APIs and their are protected by the Microsoft authentication.

In the same Tenant ID, I created 2 new app registrations for the Blazor Server and the Blazor WebAssembly. The UI can connect to the Active Directory in order to authenticate the users and the Web Assembly can call the APIs in the server project. For more details about it, see my previous post.

I define the API like the following code

[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")]
public class CurrenciesController : ControllerBase
{
    // omit the constructor and the injection
    // AzureAd:Scopes is "User.Read"

    [HttpGet]
    [Authorize]
    [RequiredScope(RequiredScopesConfigurationKey = "AzureAd:Scopes")]
    [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List<Currency>))]
    public ActionResult GetCurrencies()
    {
        return Ok(repo.GetCurrencies());
    }
}

Now, my problem is from the Web Assembly to call the External API. In the Program.cs I added

builder.Services.AddScoped<MiddletierApiAuthorizationMessageHandler>();

builder.Services.AddHttpClient("Middletier.UI.ServerAPI", client => 
    client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

builder.Services.AddHttpClient("Middletier.API", client => 
    client.BaseAddress = new Uri("https://localhost:50441"))
    .AddHttpMessageHandler<MiddletierApiAuthorizationMessageHandler>();

builder.Services.AddScoped(sp => 
    sp.GetRequiredService<IHttpClientFactory>()
      .CreateClient("Middletier.UI.ServerAPI"));

builder.Services.AddScoped(sp => 
    sp.GetRequiredService<IHttpClientFactory>()
      .CreateClient("Middletier.API"));

builder.Services.AddMsalAuthentication(options =>
{
    builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);

    // this is for the Blazor Server
    options.ProviderOptions
           .DefaultAccessTokenScopes
           .Add("api://544b368d-xxx/user.access");
});

The MiddletierApiAuthorizationMessageHandler implementation is this one

public class MiddletierApiAuthorizationMessageHandler : AuthorizationMessageHandler
{
    public MiddletierApiAuthorizationMessageHandler(IAccessTokenProvider provider,
        NavigationManager navigationManager)
        : base(provider, navigationManager)
    {
        ConfigureHandler(
            authorizedUrls: new[] { "https://localhost:7575" },
            // External API
            scopes: new[] { "api://d3de6da4-xxx/User.Read" });
    }
}

Then, in a Razor page I added

@inject IHttpClientFactory _clientFactory

@code {
    private Currency[]? currencies;

    protected override async Task OnInitializedAsync()
    {
        try
        {
            var httpClient = _clientFactory.CreateClient("Middletier.API");
            currencies = await httpClient
                                  .GetFromJsonAsync<Currency[]>("/api/v1/Currencies");
        }
        catch (AccessTokenNotAvailableException exception)
        {
            exception.Redirect();
        }
    }
}

The call is always 401 (Unauthorized). I added CORS. What do I miss?

ASP.NET Core
ASP.NET Core
A set of technologies in the .NET Framework for building web applications and XML web services.
4,400 questions
Blazor
Blazor
A free and open-source web framework that enables developers to create web apps using C# and HTML being developed by Microsoft.
1,500 questions
Microsoft Entra ID
Microsoft Entra ID
A Microsoft Entra identity service that provides identity management and access control capabilities. Replaces Azure Active Directory.
20,629 questions
{count} votes

1 answer

Sort by: Most helpful
  1. Bruce (SqlWork.com) 61,731 Reputation points
    2023-10-20T22:13:55.4966667+00:00

    If the razor page is a blazor component, then be sure you don't have this issue:

    https://github.com/dotnet/aspnetcore/issues/33241

    note: you can add a token tracer extension to the browser, so you can debug the which token is passed, and its scopes and claims. also you can see the token in the browsers network trace. use jwt.io to debug.

    0 comments No comments