Skip to content

Something wrong with AuthorizationMessageHandler in .net 6 #38486

@ghost

Description

Hello, I currently follow this doc about attaching token to outgoing request for my blazor wasm (.net 6) based on this doc ASP.NET Core Blazor WebAssembly additional security scenarios but I got 2 issues:

  • The first one I config like this
builder.Services.AddScoped<AuthorizationMessageHandler>();
builder.Services.AddScoped(sp => new HttpClient(
    sp.GetRequiredService<AuthorizationMessageHandler>()
    .ConfigureHandler(
        authorizedUrls: new[]
        {
            "http://localhost:5075/api/users",
            "http://localhost:5075/api/products",
        }
    ))
{
    BaseAddress = new Uri("http://localhost:5075/api/")
});

and when I use it. It give me error in console chrome like this

Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
      Unhandled exception rendering component: The inner handler has not been assigned.
System.InvalidOperationException: The inner handler has not been assigned.
   at System.Net.Http.DelegatingHandler.SetOperationStarted()
   at System.Net.Http.DelegatingHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Components.WebAssembly.Authentication.AuthorizationMessageHandler.<>n__0(HttpRequestMessage request, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Components.WebAssembly.Authentication.AuthorizationMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
   at System.Net.Http.Json.HttpClientJsonExtensions.<GetFromJsonAsyncCore>d__13`1[[System.Collections.Generic.List`1[[EcommerceClient.Models.ProductsModel, EcommerceClient, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext()
   at EcommerceClient.Services.ProductService.GetProductsAsync() in F:\Nguyen\Project\Hobby-Projects\Ecommerce\dotnet\EcommerceClient\Services\ProductService.cs:line 30
   at EcommerceClient.Pages.Index.OnInitializedAsync() in F:\Nguyen\Project\Hobby-Projects\Ecommerce\dotnet\EcommerceClient\Pages\Index.razor:line 11
   at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)
  • For the second one I use like this:
builder.Services.AddScoped<CustomAuthorizationMessageHandler>();
builder.Services.AddHttpClient("WebAPI", client =>
{
    client.BaseAddress = new Uri("http://localhost:5075/api/");
})
.AddHttpMessageHandler<CustomAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("WebAPI"));

CustomAuthorizationMessageHandler.cs

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

namespace EcommerceClient.Services
{
    public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler
    {
        public CustomAuthorizationMessageHandler(IAccessTokenProvider provider, NavigationManager navigation) : base(provider, navigation)
        {
            ConfigureHandler(
                authorizedUrls: new[] {
                   "http://localhost:5075/api/users",
                   "http://localhost:5075/api/products"
                });
        }
    }
}

It seems fine when first loading but when I go to another url and start use it. It gives me those error below:

Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
      Unhandled exception rendering component: The added or subtracted value results in an un-representable DateTime. (Parameter 'value')
System.ArgumentOutOfRangeException: The added or subtracted value results in an un-representable DateTime. (Parameter 'value')
   at System.DateTime.ThrowDateArithmetic(Int32 param)
   at System.DateTime.AddTicks(Int64 value)
   at System.DateTime.Add(Double value, Int32 scale)
   at System.DateTime.AddMinutes(Double value)
   at System.DateTimeOffset.AddMinutes(Double minutes)
   at Microsoft.AspNetCore.Components.WebAssembly.Authentication.AuthorizationMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at Microsoft.Extensions.Http.Logging.LoggingScopeHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
   at EcommerceClient.Services.AuthService.LoginAsync(LoginModel loginModel) in F:\Nguyen\Project\Hobby-Projects\Ecommerce\dotnet\EcommerceClient\Services\AuthService.cs:line 48
   at EcommerceClient.Pages.Auth.Login.HandleValidSubmitAsync() in F:\Nguyen\Project\Hobby-Projects\Ecommerce\dotnet\EcommerceClient\Pages\Auth\Login.razor:line 25
   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at Microsoft.AspNetCore.Components.Forms.EditForm.HandleSubmitAsync()
   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)

which indicates line 48 in my AuthService:

using System.Net.Http.Json;

namespace EcommerceClient.Services;

public class AuthService : IAuthService
{
    private readonly string Prefix = "auth";
    private readonly JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions
    {
        PropertyNameCaseInsensitive = true
    };
    private readonly ILogger<AuthService> _logger;
    private readonly HttpClient _client;
    private readonly AuthenticationStateProvider _provider;
    private readonly IStorageService _storageService;

    public AuthService(
        ILogger<AuthService> logger,
        HttpClient client,
        AuthenticationStateProvider provider,
        IStorageService storageService)
    {
        _logger = logger;
        _client = client;
        _provider = provider;
        _storageService = storageService;
    }

    public async Task<LoginResponse> LoginAsync(LoginModel loginModel)
    {
        try
        {
            var responseMessage = await _client.PostAsJsonAsync<LoginModel>($"{Prefix}/login", loginModel);
            var loginResult = await JsonSerializer.DeserializeAsync<LoginResponse>(
                await responseMessage.Content.ReadAsStreamAsync(),
                _jsonSerializerOptions
            );

            if (responseMessage.IsSuccessStatusCode)
            {
                await _storageService.SetItemAsync(Constant.JWT_TOKEN_NAME, loginResult.Token);
            }

            return loginResult!;
        }
        catch (Exception ex)
        {
            throw ex;  // This line only for debug purpose and also they indicates here ???
            return new LoginResponse { Error = ex.Message };
        }
    }

    public async Task LogoutAsync()
    {
        await _storageService.RemoveItemAsync(Constant.JWT_TOKEN_NAME);
        _client.DefaultRequestHeaders.Authorization = null;
    }

    public async Task<LoginResponse> RegisterAsync(SignupModel signupModel)
    {
        try
        {
            var responseMessage = await _client.PostAsJsonAsync<SignupModel>($"{Prefix}/signup", signupModel);
            var loginResult = await JsonSerializer.DeserializeAsync<LoginResponse>(
                await responseMessage.Content.ReadAsStreamAsync(),
                _jsonSerializerOptions
            );

            if (responseMessage.IsSuccessStatusCode)
                await _storageService.SetItemAsync(Constant.JWT_TOKEN_NAME, loginResult.Token);

            return loginResult!;
        }
        catch (Exception ex)
        {
            throw ex; // This line only for debug purpose
            return new LoginResponse { Error = ex.Message };
        }
    }
}

P\S: Before I use those everything still normal.
Thank you.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions