0

I have been migrating a client-side Blazor WebAssembly app that I had previously built that used a custom AuthenticationStateProvider. I am updating it to use an OIDC identity provider server with Microsoft's off-the-shelf RemoteAuthenticationService and their AddOidcAuthentication() method.

With the old implementation the AuthenticationStateChanged event was fired both when the user signed in via the sign-in UI AND when the user was already signed in when they started the app and the authentication was simply silently verified. None of my code was doing this, so Microsoft's authentication framework must have been handling it.

After switching to Microsoft's OIDC support the AuthenticationStateChanged event is ONLY firing when the user signs in via the UI and does NOT fire when authentication is silently verified (auth is still valid, user starts the app).

I am trying to understand how to get this event to fire the way it used to (each time the app is started, the event is fired after authentication is resolved- whether that be a route through the sign-in UI, resolved against the OIDC server silently because the token is still valid, etc.).

My configuration of the OIDC auth subsystem is quite simple (points to Microsoft):

builder.Services.AddOidcAuthentication(options => {

    // Configure our OIDC Identity Provider
    options.ProviderOptions.Authority = "{OIDC-PROVIDER-AUTHORITY-URL}";
    options.ProviderOptions.ClientId = "{OIDC-CLIENT-ID}";
    options.ProviderOptions.ResponseMode = "query";
    options.ProviderOptions.DefaultScopes.Add("email"); // openid and profile are already there by default
});

builder.Services.AddAuthorizationCore(options => {
    options.AddPolicy("IsCustomUser", policy => policy.RequireClaim("custom_user", "true"));
    options.AddPolicy("IsCustomAdmin", policy => policy.RequireClaim("custom_admin", "true"));
});

Any insights into how I can have the AuthenticationStateChanged event fire on "silent" auth verification as well?

Todd
  • 99
  • 1
  • 4

1 Answers1

0

I've found a work-around that I wanted to share, but I'll leave the question unanswered because I would be curious to know why the event is not working as expected.

For the workaround, first, you need to know that I created a subclass of RemoteAuthenticationService in order to inject some custom claims as ClaimsIdentity instances. You can see this subclass in my other answer found here.

I noticed that, although the event was not firing on "silent" auth verification my GetAuthenticationStateAsync() override in my subclass WAS getting called in each scenario. So my workaround is to fire the event myself from this method.

In my CustomAuthenticationStateProvider class I added the field "private static volatile bool IsEventFired = false;".

I then updated my override of GetAuthenticationStateAsync() like this:

public override async Task<AuthenticationState> GetAuthenticationStateAsync() {
    string? rawJwtCached = this.rawJwt;
    Task<AuthenticationState>? result = null;
    AuthenticationState authState = await base.GetAuthenticationStateAsync();
    if (authState != null) {
        ClaimsPrincipal user = authState.User;
        if (user != null) {

            AccessTokenResult tokenResult = await base.RequestAccessToken();
            if (tokenResult.TryGetToken(out AccessToken token)) {
                this.rawJwt = token.Value;
                JwtSecurityToken jwt = new JwtSecurityToken(jwtEncodedString: token.Value);
                this.claims = jwt.Claims.ToList();
                this.claimsMap = new Dictionary<string, string>();
                foreach (Claim claim in this.claims) {
                    claimsMap.Add(claim.Type, claim.Value);
                    if (claim.Type == "custom_user") { user.AddIdentity(new ClaimsIdentity(new List<Claim>() { claim })); }
                    if (claim.Type == "custom_admin") { user.AddIdentity(new ClaimsIdentity(new List<Claim>() { claim })); }
                }
                await AuthorizeFirebaseSDK(this.rawJwt);
            }
            result = Task.FromResult(new AuthenticationState(user));
        }
    }
    if ((result != null) && (!IsEventFired) && (!String.IsNullOrWhiteSpace(this.rawJwt))) {
        IsEventFired = true;
        base.NotifyAuthenticationStateChanged(result);
    } else {
        result = Task.FromResult(authState);
    }
    return (await result);
}

A couple things to note here. First, I'm using the static field IsEventFired to make sure I only fire the event once per app launch.

Also, you may notice that I am only firing it when authentication is successful, which I believe is technically wrong for the event. I think the event is supposed to fire any time the authentication state changes (like going from valid to invalid, for example). I have simplified for my case because in my situation I only care about when authentication is valid.

Todd
  • 99
  • 1
  • 4