Пользовательская аутентификация stateProvider, нарушающая аутентификацию Azure MSAL в Blazor

#c# #authentication #azure-active-directory #blazor #blazor-webassembly

Вопрос:

У меня есть веб-приложение blazor для сборки. У него есть проверка подлинности Azure AD для проверки подлинности страниц и API-это работает, У него есть проверка подлинности токенов sql JWS для проверки подлинности страниц и API — это работает

проблема в том, что я пытаюсь включить их оба. Мне нужна моя пользовательская область проверки подлинности, добавленная в program.cs на клиенте для проверки подлинности токена JWS, когда я это делаю, я получаю эту ошибку при попытке войти с помощью Azure auth ncaught (in promise) Error: System.ArgumentException: There is no event handler associated with this event. EventId: '62'. (Parameter 'eventHandlerId') at Microsoft.AspNetCore.Components.RenderTree.Renderer.DispatchEventAsync(UInt64 eventHandlerId, EventFieldInfo fieldInfo, EventArgs eventArgs) at Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer.DispatchEventAsync(UInt64 eventHandlerId, EventFieldInfo eventFieldInfo, EventArgs eventArgs) at Microsoft.AspNetCore.Components.WebAssembly.Infrastructure.JSInteropMethods.DispatchEvent(WebEventDescriptor eventDescriptor, String eventArgsJson) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) --- End of stack trace from previous location --- at Microsoft.JSInterop.Infrastructure.DotNetDispatcher.InvokeSynchronously(JSRuntime jsRuntime, DotNetInvocationInfoamp; callInfo, IDotNetObjectReference objectReference, String argsJson) at Microsoft.JSInterop.Infrastructure.DotNetDispatcher.BeginInvokeDotNet(JSRuntime jsRuntime, DotNetInvocationInfo invocationInfo, String argsJson) at Object.endInvokeDotNetFromJS (https://localhost:5001/_framework/blazor.webassembly.js:1:4191) at Object.invokeJSFromDotNet (https://localhost:5001/_framework/blazor.webassembly.js:1:3797) at Object.w [as invokeJSFromDotNet] (https://localhost:5001/_framework/blazor.webassembly.js:1:64301) at _mono_wasm_invoke_js_blazor (https://localhost:5001/_framework/dotnet.5.0.9.js:1:190800) at do_icall (<anonymous>:wasm-function[10596]:0x194e4e) at do_icall_wrapper (<anonymous>:wasm-function[3305]:0x79df9) at interp_exec_method (<anonymous>:wasm-function[2155]:0x44ad3) at interp_runtime_invoke (<anonymous>:wasm-function[7862]:0x12efff) at mono_jit_runtime_invoke (<anonymous>:wasm-function[7347]:0x118e5f) at do_runtime_invoke (<anonymous>:wasm-function[3304]:0x79d42)

Вот мой пользовательский аутентификатор stateProvider

 public class ApiAuthenticationStateProvider : AuthenticationStateProvider
{
    private readonly HttpClient _httpClient;
    private readonly ILocalStorageService _localStorage;

    public ApiAuthenticationStateProvider(HttpClient httpClient, ILocalStorageService localStorage)
    {
        _httpClient = httpClient;
        _localStorage = localStorage;
    }
    public override async Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        var savedToken = await _localStorage.GetItemAsync<string>("authToken");

        if (string.IsNullOrWhiteSpace(savedToken))
        {
            return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
        }

        _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", savedToken);

        return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(savedToken), "jwt")));
    }

    public void MarkUserAsAuthenticated(string email)
    {
        var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, email) }, "apiauth"));
        var authState = Task.FromResult(new AuthenticationState(authenticatedUser));
        NotifyAuthenticationStateChanged(authState);
    }

    public void MarkUserAsLoggedOut()
    {
        var anonymousUser = new ClaimsPrincipal(new ClaimsIdentity());
        var authState = Task.FromResult(new AuthenticationState(anonymousUser));
        NotifyAuthenticationStateChanged(authState);
    }

    private IEnumerable<Claim> ParseClaimsFromJwt(string jwt)
    {
        var claims = new List<Claim>();
        var payload = jwt.Split('.')[1];
        var jsonBytes = ParseBase64WithoutPadding(payload);
        var keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes);

        keyValuePairs.TryGetValue(ClaimTypes.Role, out object roles);

        if (roles != null)
        {
            if (roles.ToString().Trim().StartsWith("["))
            {
                var parsedRoles = JsonSerializer.Deserialize<string[]>(roles.ToString());

                foreach (var parsedRole in parsedRoles)
                {
                    claims.Add(new Claim(ClaimTypes.Role, parsedRole));
                }
            }
            else
            {
                claims.Add(new Claim(ClaimTypes.Role, roles.ToString()));
            }

            keyValuePairs.Remove(ClaimTypes.Role);
        }

        claims.AddRange(keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString())));

        return claims;
    }

    private byte[] ParseBase64WithoutPadding(string base64)
    {
        switch (base64.Length % 4)
        {
            case 2: base64  = "=="; break;
            case 3: base64  = "="; break;
        }
        return Convert.FromBase64String(base64);
    }

 
}
 

}

and I am adding it in to program.cs by

  builder.Services.AddScoped<AuthenticationStateProvider, ApiAuthenticationStateProviderClient>();
 

и запуск его с помощью этой службы аутентификации учетной записи

  public interface IAccountService
{
    User User { get; }
    Task Initialize();
    Task<LoginResult> Login(LoginRequest model);
    Task Logout();
}

public class AccountService : IAccountService
{
   // private IHttpService _httpService;
    private NavigationManager _navigationManager;
  //  private ILocalStorageService _localStorageService;
    private string _userKey = "user";
    private readonly HttpClient _httpService;
    private readonly AuthenticationStateProvider _authenticationStateProvider;
    private readonly ILocalStorageService _localStorage;

    public User User { get; private set; }

    public AccountService(
        HttpClient httpService,
        AuthenticationStateProvider authenticationStateProvider,
        ILocalStorageService localStorageService
    ) {
        _httpService = httpService;
        _authenticationStateProvider = authenticationStateProvider;
        _localStorage = localStorageService;
    }

    public async Task Initialize()
    {
        User = await _localStorage.GetItemAsync<User>(_userKey);
    }

    public async Task<LoginResult> Login(LoginRequest model)
    {
        AuthCredentials authCredentials = new AuthCredentials()
        {
            Username = model.Email,
            Password = model.Password
        };
        try
        {

            var loginAsJson = JsonSerializer.Serialize(authCredentials);
            var response = await _httpService.PostAsync("api/auth/login", new StringContent(loginAsJson, Encoding.UTF8, "application/json"));
            var loginResult = JsonSerializer.Deserialize<LoginResult>(await response.Content.ReadAsStringAsync(), new JsonSerializerOptions { PropertyNameCaseInsensitive = true });


            if (loginResult.Successful == false)
                return null;

            await _localStorage.SetItemAsync("authToken", loginResult.Token);


            ((ApiAuthenticationStateProviderClient)_authenticationStateProvider).NotifyUserAuthentication(loginResult.Token);
            _httpService.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", loginResult.Token);
            return loginResu<
        }
        catch (Exception ex)
        {
            string message = ex.Message;
            return null;
        }
       
    }

    public async Task Logout()
    {


        await _localStorage.ClearAsync();
        ((ApiAuthenticationStateProviderClient)_authenticationStateProvider).MarkUserAsLoggedOut();
        _httpService.DefaultRequestHeaders.Clear();
        _navigationManager.NavigateTo("/");

    
    }
}
 

}

любая помощь будет очень признательна. Я знаю, что оба метода аутентификации работают на 100%, если я разрешу только один из них. Просто не могу ни за что на свете получить это, чтобы пользователи могли пройти аутентификацию с помощью любого из них в зависимости от того, какую кнопку входа они нажимают

Ответ №1:

Это известная ошибка в blazor.

Ошибка, о которой вы упомянули выше, означает, что у вас проблема с обработчиком событий.

Да, Microsoft выпустила новую версию, вы можете обновить свое приложение.

Для обходного пути вы можете использовать await Task.Yield() .

Например, вот как будет выглядеть обновление вашего кода:

 private a sync Task Callback(KeyboardEventArgs args)
{
    if (args.Key == "Enter")
    {
        await Task.Yield();
        showInput = false;
    }
}
 

Комментарии:

1. Спасибо, я решил использовать Auth0, так как не думал, что есть обходной путь