I have a standalone Blazor WASM site (client), a separate .NET 6 web API (server) with protected endpoints and I'm trying to call MS Graph from the API.
I've read just about every article I could find on the configuration required to make this work and I'm stuck with the incremental consent failing. I get the following error when trying to access a server API which uses MS Graph:
Error acquiring a token for a downstream web API - MsalUiRequiredException message is: AADSTS65001: The user or administrator has not consented to use the application with ID '[redacted]' named '[redacted]'. Send an interactive authorization request for this user and resource.
Configuration...
Created AAD app for Web API (server), added secret for Graph configuration, set the app URI and created
access_as_userscope under "Expose an API" in AAD.Added the client ID (from the following step) to the
knownClientApplicationssection in the manifest for the server app registration in AAD.For API Permissions I added Graph scopes
User.Read,User.Read.All, andGroup.Read.Alland provided admin consent in the AAD UI.Configured
appsettings.jsonin the API to add the Graph APIBaseUrland above scopes from step 2 along with the correct AzureAD domain,TenantId,ClientId, andClientSecretvalues for MSAL to function.Configured MSAL on the server:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration)
.EnableTokenAcquisitionToCallDownstreamApi()
.AddMicrosoftGraph(builder.Configuration.GetSection("MicrosoftGraph"))
.AddInMemoryTokenCaches();
Created AAD app for Blazor WASM, used SPA auth w/redirect to
https://localhost:7014/authentication/login-callbackand set the API permissions toapi://[redacted]/access_as_useronly.Created custom authorization message handler according to this article.
public CustomAuthorizationMessageHandler(IAccessTokenProvider provider, NavigationManager navigation) : base(provider, navigation)
{
ConfigureHandler(
authorizedUrls: new[]
{
"https://localhost:7069"
},
scopes: new[]
{
"api://[redacted]/.default"
});
}
- Configured MSAL on the client:
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("api://[redacted]/.default");
options.ProviderOptions.LoginMode = "redirect";
}
- Set up named HTTP client on the Blazor client with custom message handler:
var baseAddress = builder.Configuration["PublicApiUrl"];
builder.Services.AddHttpClient("PublicApi", client =>
{
client.BaseAddress = new Uri(baseAddress);
}).AddHttpMessageHandler<CustomAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("PublicApi"));
builder.Services.AddScoped<CustomAuthorizationMessageHandler>();
What works...
- I can authenticate as an AAD user to the Blazor client.
- I can access protected endpoints (using policy-based authorization) hosted on the server which don't have a dependency on MS Graph.
Questions...
Following this article's guidance about incremental consent, specifically the "Static permissions" section, I would assume granting admin consent for Graph on the server's app registration would suffice?
All of the documentation showing Blazor WASM with a protected API calling a protected API (Graph) assume the Blazor client is also hosted by the API server. Is it even possible to use on-behalf-of flow in my case? If it was hosted I could see the API calling the Blazor navigation subsystem to perform an incremental consent redirect but when they're separated, I can only imagine the static permissions is the way to go.
Is it necessary to set the
DefaultAccessTokenScopesin the client?