Blazor WASM (standalone) B2C auth with Azure Function App authentication

Viewed 300

I am working on a Blazor WASM standalone (not ASP.NET core hosted) application which has been integrated with my B2C tenancy for user auth. I'm able to successfully authentication against B2C without issue.

In another Azure directory I have an Azure Function App with Authentication enabled. I'm using Microsoft authentication with auth set to use the same B2C tenancy. As a test I can confirm that I'm able to successfully call the various functions using auth by passing the JWT bearer token in the header of the API request. Add the following to the httpClient achieves this

httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "eyJ0eXAiOiJKV1QiLCJhbGciOiJS_WTAiGTyfEHuXjlFh5FOo8hBk1V7GFjzUn4TUOHIOjg3uDAaL730J8PewVvE8xyaG1MllR1JRjDz1D95bMch7QMqXZ-sRuUzvWdvBK_IkQ…");

However this is obviously only good as a test.

What I'm trying to achieve is to have the Bearer token obtained during the initial B2C signin request used when making subsequent API calls.....however I cant seem to figure out how to do this in Blazor WASM. I believe in Blazor Server this can be done via

var accessToken = await HttpContext.GetTokenAsync("access_token");
var refreshToken = await HttpContext.GetTokenAsync("refresh_token");

Reviewing the article link at How to secure an Azure Function accessed by a Blazor Wasm app with Azure AD B2C? I have followed the details listed (noting there are differences between the newer "App Registration" and "Applications (Legacy)") however haven't had much luck. When calling the FetchData page it simply doesn’t appear to be passing a token.

Here is a copy of my Program.cs page:

using B2CBlazorWASMNotHosted;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using System.Net.Http;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
// register CustomAuthorizationMessageHandler 
builder.Services.AddScoped<CustomAuthorizationMessageHandler>();

// configure httpclient
// call the following code please add packageMicrosoft.Extensions.Http 3.1.0
builder.Services.AddHttpClient("ServerAPI", client =>
  client.BaseAddress = new Uri("https://myfunctionapp.azurewebsites.net"))
        .AddHttpMessageHandler<CustomAuthorizationMessageHandler>();

// register the httpclient
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
 .CreateClient("ServerAPI"));

builder.Services.AddMsalAuthentication(options =>
{
    builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
      options.ProviderOptions.LoginMode = "redirect";
    options.ProviderOptions.DefaultAccessTokenScopes.Add("https://myb2ctenant.onmicrosoft.com/8f7ece0f-a4ef-4f96-9a24-fc4e2ced9504/AccessAPI");

});


await builder.Build().RunAsync();
public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler
{
    public CustomAuthorizationMessageHandler(IAccessTokenProvider provider,
        NavigationManager navigationManager)
        : base(provider, navigationManager)
    {
        ConfigureHandler(
            authorizedUrls: new[] { "myfunctionapp.azurewebsites.net" },
            scopes: new[] { "myb2ctenant.onmicrosoft.com/8f7ece0f-a4ef-4f96-9a24-fc4e2ced9504/AccessAPI" });
    }
}

Within the Azure Portal I currently have 2 different App registrations. 1 for the Blazor WASM app auth and another for the Function App. Screenshot of the app reg settings are as follows:

B2C App Registrations Blazor App Registration Function App Registration

The code calling the API is here:

var httpClient = new HttpClient();
var source = new CancellationTokenSource();
var centreguid = new Centre { centreguid = "CA8E99BC-F5B0-4A54-A8BF-C9AE9E95EE44" };
   
var response = await httpClient.PostAsJsonAsync("https://myfunctionapp.azurewebsites.net/api/GetArenasForCentre?", centreguid, source.Token);
string stringresponse = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<parentArenas>(stringresponse);

arenas = result.ArenaDetails;

Console.WriteLine("json output:");

If I put a breakpoint on var result I can see the following message being returned:

401 "You do not have permission to view this directory or page."

Is anyone able to point me in the right direction here please as to what I'm doing wrong? Perhaps there is a better way of achieving this.


Some additional info:

As opposed to using a separate app registration for the function app I've also tried using the same SPA app reg and have configured the Function App auth to use this app reg instead....to no avail unfortunately. I've also set the same APIAccess scope on this app registration (to be honest I'm not even sure if the app scopes are required for this)

1 Answers

I expose an API in the portal in the API's App registration:

enter image description here

On the client I add an API Permission

enter image description here

enter image description here

On the WASM client you need to add the request for the scope to config.

enter image description here

Related