2

I'm working on a Blazor WASM app, and have managed to get it working as a Static Web App using Google authentication (we do not want to use any other type of auth). I am using Bring Your Own Functions as well for my Azure Function API.

The way it works, is that once a user logs in using their Google account, my app takes their email, and compares it to a list of "registered users" in a database to determine the role of the user. I then use a custom account factory to add these roles, and this allows me to show/hide various elements of my WASM app depending on the roles of the currently logged in user. All of this works both in dev and once I publish the Static Web App to Azure.

The challenge is that I cannot seem to secure the API, it's completely open at the moment but I want it to only be available for certain roles. I've done a tonne of research on the internet, but everything seems fairly out of date (I'm using NET 5.0, since Static Web Apps don't seem to support NET 6.0 preview yet). So far I've got the following options:

  1. Use EasyAuth instead, and secure the API using routes in staticwebapp.config.json. This sorts out my API authorisation issue completely, but the .auth/me endpoint only gives me the user's email address (not all the other Google profile info that I currently get using the OIDC authorization flow). I also need to add the relevant roles from the database so they are emitted when calling the API.

  2. Pass the Google access token to the HTTP request via AddHttpMessageHandler(), which seems to work as I see a "Bearer XXX" in the authorisation header, but I'm not sure how to get my Azure Function API to consume it. Also - this doesn't take care of the roles requirement, somehow I need to get the user info from the token as well.

Which of the above two makes more sense or am I missing a more obvious third option?

Please note that the users and roles need to be added via the app, not via the Azure portal, hence why I'm using a database to store the email addresses and roles.

My WASM code is as follows:

public class Program
    {
        public static async Task Main(string[] args)
        {

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



            var baseAddress = builder.Configuration["BaseAddress"] ?? builder.HostEnvironment.BaseAddress;


            builder.Services.AddHttpClient("MyApp.Api", client => client.BaseAddress = new Uri(baseAddress))
                .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>(); ;
            builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("MyApp.Api"));
 
            builder.Services.AddOidcAuthentication(options =>
            {
                builder.Configuration.Bind("Local", options.ProviderOptions);
                options.ProviderOptions.DefaultScopes.Add("email");
            })
            .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, RemoteUserAccount, CustomAccountFactory>();
            ;


            await builder.Build().RunAsync();
        }
    }

CustomAccountFactory.cs


public class CustomAccountFactory
    : AccountClaimsPrincipalFactory<RemoteUserAccount>
{

    IHttpClientFactory _httpFactory;

    public CustomAccountFactory(NavigationManager navigationManager,
        IAccessTokenProviderAccessor accessor, IHttpClientFactory httpClient) : base(accessor)
    {
        _httpFactory = httpClient; 
    }

    public async override ValueTask<ClaimsPrincipal> CreateUserAsync(RemoteUserAccount account, RemoteAuthenticationUserOptions options)
    {
        var initialUser = await base.CreateUserAsync(account, options);

        if (initialUser.Identity.IsAuthenticated)
        {
            var email = initialUser.FindFirst("email")?.Value;

            try
            {
                var _httpClient = _httpFactory.CreateClient("MyApp.Api");
                var userRole = await _httpClient.GetFromJsonAsync<string>($"api/UserRole?email={email}");
                ((ClaimsIdentity)initialUser.Identity).AddClaim(new Claim(ClaimTypes.Role, userRole));
            }
            catch (Exception ex)
            {
                throw new Exception($"Could not get user roles from the API.  Please check it is running. {ex.Message} {ex.InnerException?.Message}");
            }

        }

        return initialUser;
    }
}
4

0 回答 0