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