Reputation: 148
I have a blazor web assembally application. It has Azure AD authentication to authenticate the pages and the API -that works It has sql JWS token authentication to authenticate pages and the API - that works
the problem is when I am trying to have them both enabled.
I need my custom AuthenticationStateProvider scope added to program.cs on the client for the JWS token auth, when I do I get this error when trying to sign in with 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, DotNetInvocationInfo& 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)
Here is my custom AuthenticationStateProvider
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>();
and triggering it with this account auth service
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 loginResult;
}
catch (Exception ex)
{
string message = ex.Message;
return null;
}
}
public async Task Logout()
{
await _localStorage.ClearAsync();
((ApiAuthenticationStateProviderClient)_authenticationStateProvider).MarkUserAsLoggedOut();
_httpService.DefaultRequestHeaders.Clear();
_navigationManager.NavigateTo("/");
}
}
}
any help would be greatly appreciated. I know both methods of authentication are 100% working if I only allow one of them. Just cannot for the life of me get it so users can authenticate with either depending on what login button they click
Upvotes: 2
Views: 1399
Reputation: 805
This is a known bug in blazor.
The error you mentioned above means that you have an issue with your event handler.
Yes, Microsoft released the new version, you can update your app.
For a workaround, you can use await Task.Yield()
.
For example, this is what updating your code would look like:
private a sync Task Callback(KeyboardEventArgs args)
{
if (args.Key == "Enter")
{
await Task.Yield();
showInput = false;
}
}
Upvotes: -1