问题
I am using an open id connect identity provider for my Blazor wasm application and would like an access token to be attached to the http client as described in this article.
However, whenever I create the http client and try to use it I am getting an AccessTokenNotAvailableException, even when logged in.
Here is what I have:
In Program.cs
// Add service for CustomAuthorizationMessageHandler
builder.Services.AddScoped<CustomAuthorizationMessageHandler>();
// Http client for requests to API
builder.Services.AddHttpClient("API", client =>
{
// Set base address to API url
client.BaseAddress = new Uri("https://localhost:44370");
// Set request headers to application/json
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
}).AddHttpMessageHandler<CustomAuthorizationMessageHandler>();
// Auth0 authentication setup
builder.Services.AddOidcAuthentication(options =>
{
builder.Configuration.Bind("Auth0", options.ProviderOptions);
options.ProviderOptions.ResponseType = "code";
});
CustomAuthorizationHandler.cs
public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler
{
public CustomAuthorizationMessageHandler(IAccessTokenProvider provider,
NavigationManager navigationManager)
: base(provider, navigationManager)
{
ConfigureHandler(
authorizedUrls: new[] { "https://localhost:44370" },
scopes: new[] { "admin" });
}
}
When trying to access the api
try
{
var client = ClientFactory.CreateClient("API");
message = await client.GetStringAsync("api/message");
}
catch (AccessTokenNotAvailableException e)
{
message = "You CANNOT access the api. Please log in";
}
Currently, the below code works to get the message from the api when removing the AddHttpMessageHandler call in Program.cs but I don't want to have to get the access token every time I make an api call. TokenProvider is of type IAccessTokenProvider and is injected in.
var client = ClientFactory.CreateClient("API");
using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, "api/message"))
{
var tokenResult = await TokenProvider.RequestAccessToken();
if (tokenResult.TryGetToken(out var token))
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.Value);
var response = await client.SendAsync(requestMessage);
if(response.IsSuccessStatusCode)
{
message = await response.Content.ReadFromJsonAsync<string>();
}
else
{
message = await response.Content.ReadAsStringAsync();
}
}
else
{
message = "You CANNOT access the api. Please log in";
}
}
How can I fix this so I don't get an AccessTokenNotAvailableException every time?
回答1:
It has clearly been stated that there are two ways to configure a message handler for outgoing requests, recommending implementing a custom message handler. Once again.........you implement a custom message handler in order to CONFIGURE the message handler. That's the sole purpose of the customization. Of course, you may override the SendAsync method, if such an action is in place, you know what you do, and you do that appropriately. But not as a way to introduce bugs and actually make the SendAsync invalid.
Here's the implementation of the custom message handler:
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[] { "https://localhost:44370" });
}
}
The above solve the issue you had...that is to say, configuring the scopes property (scopes: new[] { "admin" });
), which can be left out.
The following are some suggestions to improve your code: Create a named HttpClient service as follows.
builder.Services.AddTransient(sp => sp.GetRequiredService<IHttpClientFactory>
().CreateClient("ServerAPI"));
And inject it into your component like this: @inject HttpClient Http
And use it like this:
protected override async Task OnInitializedAsync()
{
try
{
message= await Http.GetFromJsonAsync<string>("api/message");
}
catch (AccessTokenNotAvailableException exception)
{
exception.Redirect();
}
}
Note: In order to use the GetFromJsonAsync method, you should install the System.Net.Http.Json package
Note that the above is the proper way to handle the Http request. That includes redirecting the user to a login page. Of course you may produce whatever message you want to display to your user, but the redirection is the proper way.
After applying the suggested changes above, your Program.Main method should have the following settings (pay attention to order):
builder.Services.AddScoped<CustomAuthorizationMessageHandler>();
builder.Services.AddHttpClient("ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<CustomAuthorizationMessageHandler>();
builder.Services.AddTransient(sp =>
sp.GetRequiredService<IHttpClientFactory>().CreateClient("ServerAPI"));
来源:https://stackoverflow.com/questions/63076954/automatically-attaching-access-token-to-http-client-in-blazor-wasm