Reputation: 1456
I have a Blazor Webassembly app that is using Azure B2C to authenticate and authorize users. I am able to successfully log in and the token is generated, but when I try to call my API, there is no token in the request header.
I followed this guide: https://learn.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/standalone-with-azure-active-directory-b2c?view=aspnetcore-7.0
Here's my Program.cs
var builder = WebAssemblyHostBuilder.CreateDefault(args);
var baseAddress = builder.Configuration.GetValue<string>("BaseUrl");
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddHttpClient("WebAPI", client => client.BaseAddress = new Uri(baseAddress))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
.CreateClient("WebAPI"));
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAdB2C", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("https://{domain}.onmicrosoft.com/{App ID}/Api.Access");
});
await builder.Build().RunAsync();
Am I missing a line to add the token to the HttpClient?
Upvotes: 0
Views: 902
Reputation: 2036
Following @Tiny Wang answer I have discovered that, if you use BaseAddressAuthorizationMessageHandler
it will only send authorization bearer
if the call is inside navigationManager.BaseUri
:
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Net.Http;
namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication;
/// <summary>
/// A <see cref="DelegatingHandler"/> that attaches access tokens to outgoing <see cref="HttpResponseMessage"/> instances.
/// Access tokens will only be added when the request URI is within the application's base URI.
/// </summary>
public class BaseAddressAuthorizationMessageHandler : AuthorizationMessageHandler
{
/// <summary>
/// Initializes a new instance of <see cref="BaseAddressAuthorizationMessageHandler"/>.
/// </summary>
/// <param name="provider">The <see cref="IAccessTokenProvider"/> to use for requesting tokens.</param>
/// <param name="navigationManager">The <see cref="NavigationManager"/> used to compute the base address.</param>
public BaseAddressAuthorizationMessageHandler(IAccessTokenProvider provider, NavigationManager navigationManager)
: base(provider, navigationManager)
{
ConfigureHandler(new[] { navigationManager.BaseUri });
}
}
You have to create a custom handler like this and specify the domains where you want to include authorization bearer
:
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Net.Http;
namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication;
/// <summary>
/// A <see cref="DelegatingHandler"/> that attaches access tokens to outgoing <see cref="HttpResponseMessage"/> instances.
/// Access tokens will only be added when the request URI is within the application's base URI.
/// </summary>
public class BaseAddressAuthorizationMessageHandler_Custom : AuthorizationMessageHandler
{
/// <summary>
/// Initializes a new instance of <see cref="BaseAddressAuthorizationMessageHandler"/>.
/// </summary>
/// <param name="provider">The <see cref="IAccessTokenProvider"/> to use for requesting tokens.</param>
/// <param name="navigationManager">The <see cref="NavigationManager"/> used to compute the base address.</param>
public BaseAddressAuthorizationMessageHandler_Custom(IAccessTokenProvider provider, NavigationManager navigationManager)
: base(provider, navigationManager)
{
ConfigureHandler(new[] { navigationManager.BaseUri, "https://localhost:7039" ❗❗❗ });
}
}
And then use in your program like this:
builder.Services.AddScoped<BaseAddressAuthorizationMessageHandler_Custom>();
builder.Services.AddHttpClient<IEmployeeDataService, EmployeeDataService>(client => client.BaseAddress =
new Uri("https://localhost:7039")).AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler_Custom>();
builder.Services.AddHttpClient<ICountryDataService, CountryDataService>(client => client.BaseAddress =
new Uri("https://localhost:7039")).AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler_Custom>();
builder.Services.AddHttpClient<IJobCategoryDataService, JobCategoryDataService>(client => client.BaseAddress =
new Uri("https://localhost:7039")).AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler_Custom>();
Then, in server side, be sure to allow the origin for avoid CORS
errors:
builder.Services.AddCors(options =>
{
options.AddPolicy(name: "_myAllowSpecificOrigins",
policy =>
{
policy.WithOrigins("http://localhost:5109")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
...
app.UseRouting();
app.UseCors("_myAllowSpecificOrigins");
app.UseAuthentication();
app.UseAuthorization();
Upvotes: 0
Reputation: 16066
I had a blazor app which is https://localhost:7280
and it doesn't contain an API. And I also had another api project which had API https://localhost:7018/WeatherForecast
. Then when I write code like this:
builder.Services.AddHttpClient("WebAPI",client => client.BaseAddress = new Uri("https://localhost:7018"))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
result = await Http.GetStringAsync("/WeatherForecast");
the request would send without bearer token in the request header and would get 401 error.
And we can also see the token is already generated.
But when I write code like this:
builder.Services.AddHttpClient("WebAPI",client => client.BaseAddress = new Uri("https://localhost:7280"))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
result = await Http.GetStringAsync("/WeatherForecast");
There would be an access token inside the request.
I'm afraid this is what this sentence means.... So we can only manually generate the token and add it in request header like what I shared below.
==================================================
I had a test in my side and I met your issue in my side as well and this is what I used to generate token manually to call the api. use the code I commented.
@page "/profile"
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
@attribute [Authorize]
@inject IAccessTokenProvider TokenProvider
@inject HttpClient Http
<h3>User Profile</h3>
<button @onclick="call">
call api
</button>
<div>@result</div>
@code {
private string result = "no data now";
private async Task call()
{
try
{
result = await Http.GetStringAsync("https://localhost:7018/WeatherForecast");
}
catch (AccessTokenNotAvailableException exception)
{
exception.Redirect();
}
//var http = new HttpClient();
//http.BaseAddress = new Uri("https://localhost:7018/WeatherForecast");
//var tokenResult = await TokenProvider.RequestAccessToken(
// new AccessTokenRequestOptions
// {
// Scopes = new[] { "api://xxxx/Tiny.Read" }
// });
//if (tokenResult.TryGetToken(out var token))
//{
// http.DefaultRequestHeaders.Add("Authorization",
// $"Bearer {token.Value}");
// result = await http.GetStringAsync("https://localhost:7018/WeatherForecast");
//}
}
}
Upvotes: 2