0

I’m trying to use Microsoft.Identity.Web to call an API from my .NET 6 Blazor server app. I can successfully login to authorized pages in my Blazor app but when I try to make an API call using CallWebApiForUserAsync I get an inner exception of ā€œNo account or login hint was passed to the AcquireTokenSilent callā€.

The couple of posts here on Stack Overflow and the MS documentation look to me like I have it configured correctly. What am I missing?

appsettings.json:

  "AzureAd": {
    "Authority": "https://hivercom.b2clogin.com/XXXXXX.onmicrosoft.com/B2C_1_SignUpSignIn",
    "Instance": "https://XXXXXX.b2clogin.com",
    "TenantId": "XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX",
    "ClientId": "XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX",
    "CallbackPath": "/signin-oidc",
    "Domain": "XXXXXX.onmicrosoft.com",
    "SignedOutCallbackPath": "/signout/B2C_1_susi",
    "SignUpSignInPolicyId": "B2C_1_SignUpSignIn",
    "ResetPasswordPolicyId": "B2C_1_PasswordReset",
    "EditProfilePolicyId": "B2C_1_EditProfile",
    "ClientSecret": "XXXXXXXXXXXXXXXXXX"
  },
  "HiverAPI": {
    "BaseUrl": "https://localhost:7075",
    "Scopes": "https://XXXXXX.onmicrosoft.com/XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX/all"
  },

Program.cs

using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.UI;
using BlazorB2C.Data;
using Microsoft.Identity.Web.TokenCacheProviders.InMemory;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Authorization;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpClient();

builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, "AzureAd")
    .EnableTokenAcquisitionToCallDownstreamApi(new string[] { "https://XXXXXX.onmicrosoft.com/XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX/all" })
    .AddDownstreamWebApi("HiverService", builder.Configuration.GetSection("HiverAPI"))
    .AddInMemoryTokenCaches();

builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = options.DefaultPolicy;
});

builder.Services.AddRazorPages().AddMvcOptions(options =>
{
    var policy = new AuthorizationPolicyBuilder()
              .RequireAuthenticatedUser()
              .Build();
    options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();

builder.Services.AddServerSideBlazor()
    .AddMicrosoftIdentityConsentHandler();

builder.Services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
    options.Events.OnSignedOutCallbackRedirect = context =>
    {
        context.HttpContext.Response.Redirect(context.Options.SignedOutRedirectUri);
        context.HandleResponse();
        return Task.CompletedTask;
    };
});

builder.Services.AddSingleton<WeatherForecastService>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseDefaultFiles();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");

app.Run();

Level.razor:

@page "/level"
@using Microsoft.Identity.Web

@inject IHttpClientFactory HttpClientFactory
@inject Microsoft.Identity.Web.ITokenAcquisition TokenAcquisitionService
@inject Microsoft.Identity.Web.IDownstreamWebApi DownstreamWebApi
@inject MicrosoftIdentityConsentAndConditionalAccessHandler ConsentHandler

@attribute [Authorize]
@attribute [AuthorizeForScopes(ScopeKeySection = "HiverAPI:Scopes")]

<PageTitle>Level</PageTitle>

<a href="MicrosoftIdentity/Account/SignOut">Log out</a>

<h3>Level</h3>

@code {
    private const string ServiceName = "HiverService";

    protected override async Task OnInitializedAsync()
    {

        try
        {
        error >> string token = await TokenAcquisitionService.GetAccessTokenForUserAsync(new string[] { "https://XXXXXX.onmicrosoft.com/XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX/all" });

        error >> var value = await DownstreamWebApi.CallWebApiForUserAsync(
                ServiceName,
                options =>
                {
                    options.RelativePath = $"HiverUser";
                });
        }
        catch (Exception ex)
        {
            var message = ex.Message;

            ConsentHandler.HandleException(ex);
        }

        try
        {
        error >> await DownstreamWebApi.CallWebApiForUserAsync(
                ServiceName,
                options =>
                {
                    options.HttpMethod = HttpMethod.Put;
                    options.RelativePath = $"HiverUser/0fd30e94-a116-4ef0-b222-4125546b8561";
                });
        }
        catch (Exception ex)
        {
            var message = ex.Message;

            ConsentHandler.HandleException(ex);
        }
    }    
}

Azure configuration for client app: enter image description here

enter image description here

enter image description here

Azure configuration for API

enter image description here

enter image description here

Alex
  • 1,681
  • 2
  • 11
  • 18

1 Answers1

2

CallWebApiForAppAsync uses the on-behalf flow, which is not available for Azure AD B2C.

https://github.com/AzureAD/microsoft-identity-web/wiki/b2c-limitations

You cannot use ITokenAcquisition.GetTokenForAppAsync or IDownstreamApi.CallWebApiForAppAsync in Azure AD B2C web apps.

https://learn.microsoft.com/en-us/azure/active-directory-b2c/access-tokens

As an alternative, you can request access tokens for downstream APIs(Hiver.API in your case) using GetAccessTokenForUserAsync. The resulting access token can be used to call the API. You'll need to use a standard HttpClient instead of IDownstreamWebApi. Full example can be found here:

Relevant code from Github:

private async Task PrepareAuthenticatedClient()
{
    var accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(new[] { _TodoListScope });
    Debug.WriteLine($"access token-{accessToken}");
    _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
    _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
Alex AIT
  • 17,361
  • 3
  • 36
  • 73