Search code examples
azure-active-directoryblazorblazor-client-sideazure-function-app

Blazor Client side get CORS error when accessing Azure Function using Azure Active directory


I've been trying to deploy my Blazor PWA for 2 days without any success so far, if someone has an idea of what I’m doing wrong I would be really grateful. hello I could resolve most of my issues by myself but I'm now stuck on a CORS problem using AAD.

Here's my project setup:

  • Blazor webassembly client hosted on Static Website Storage (works great), Net 5
  • AzureFunctions connected to an Azure Sql Server database (works great with anonymous authentication in Blazor)
  • Azure Active Directory I want to use to authenticate the users. (protecting both the blazor app and the functions)

So I’ve created an App registration, added my static web site address as SPA uri and uncheck both implicit. In my blazor client, program.cs, I’ve added the following code to connect to AAD:

builder.Services.AddMsalAuthentication(options =>
{
    builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication); //contains clientId, Authority
    options.ProviderOptions.DefaultAccessTokenScopes.Add("https://graph.microsoft.com/User.Read");
    options.ProviderOptions.LoginMode = "redirect";
});

That works great too, I can login, authorize view works as expected.

The problem is when I try to authenticate Azure functions with «Login with Azure Active Directory», I' tried with both express and advanced configurations (using clientId, tenantId) but when I

Access to fetch at 'https://login.windows.net/tenantid/oauth2/authorize ... (redirected from 'https://myfunctionstorage.azurewebsites.net/api/client/list') from origin 'https://*****9.web.core.windows.net' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

I have of course enabled CORS for my Blazor Client Address on Azure Functions configuration but the problem seems to be about the login windows uri...

Also, if I enable the token id implicit flow in the App registration and access the login url in the browser it works perfectly fine.

All the examples I could find so far are about msal 1.0 and App registration using implicit flow instead of SPA so it doesn't help...

Thank you for your time and your help.

UPDATE: I’ve done more researches since yesterday and I think it could by related to my HTTPClient, currently I use the basic one (with just a base adress).

But I’ve seen on some example that when using the Client using AAD it needs more parameters, for example:

builder.Services.AddHttpClient("companiesAPI", cl => { cl.BaseAddress = new Uri("https://localhost:5001/api/"); }) .AddHttpMessageHandler(sp => { var handler = sp.GetService<AuthorizationMessageHandler>() .ConfigureHandler( authorizedUrls: new[] { "https://localhost:5001" }, scopes: new[] { "companyApi" } ); return handler; });

Is that AuthorizationMessageHandler needed ? Also I see some references to the need of using the «use_impersonation» scope.

Are those changes (on HttpClient and the use_impersonation scope) also required when using msal2/SPA app registration ?

Thank you


Solution

  • If want to call the Azure function projected by Azure AD in Blazor webassembly client, please refer to the following steps If you want to call Azure function projected by Azure AD in angular application, please refer to the following code

    • Create Azure AD application to protect function

      1. Register Azure AD application. After doing that, please copy Application (client) ID and the Directory (tenant) ID

      2. Configure Redirect URI. Select Web and type <app-url>/.auth/login/aad/callback.

      3. Enable Implicit grant flow

      4. Define API scope and copy it enter image description here

      5. Create client secret.

    • Enable Azure Active Directory in your App Service app

    • Configure CORS policy in Azure Function enter image description here

    • Create Client AD application to access function

      1. Register application
      2. Enable Implicit grant flow
      3. configure API permissions. Let your client application have permissions to access function
    • Code

    1. Create Custom AuthorizationMessageHandler class
    using Microsoft.AspNetCore.Components;
    using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
    
    public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler
    {
        public CustomAuthorizationMessageHandler(IAccessTokenProvider provider, 
            NavigationManager navigationManager)
            : base(provider, navigationManager)
        {
            ConfigureHandler(
                authorizedUrls: new[] { "<your function app url>" },
                scopes: new[] { "<the function app  API scope your define>" });
        }
    }
    
    1. Add the following code in Program.cs.
     public static async Task Main(string[] args)
            {
                var builder = WebAssemblyHostBuilder.CreateDefault(args);
                builder.RootComponents.Add<App>("app");
                 // 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("<your function app url>"))
                        .AddHttpMessageHandler<CustomAuthorizationMessageHandler>();
                // register the httpclient
                builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
                 .CreateClient("ServerAPI"));
                // configure Azure AD auth
                builder.Services.AddMsalAuthentication(options =>
                {
                    builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
                    options.ProviderOptions.DefaultAccessTokenScopes.Add("<the function app  API scope your define>");
    
    
                });
    
                await builder.Build().RunAsync();
            }
    
    1. Call the API
    @page "/fetchdata"
    @using Microsoft.AspNetCore.Components.WebAssembly.Authentication
    @inject HttpClient Http
    
    <h1>Call Azure Function</h1>
    
    <p>This component demonstrates fetching data from the server.</p>
    
    <p>Result: @forecasts</p>
    
    <button class="btn btn-primary" @onclick="CallFun">Call Function</button>
    
    @code {
        private string forecasts;
    
        protected async Task CallFun()
        {
            forecasts = await Http.GetStringAsync("api/http");
        }
    
    
    }
    

    enter image description here