Search code examples
asp.net-coreasp.net-identityblazor-server-side

Can I force AuthenticationState to refresh (when claims have changed)?


Is there any way to force the AuthenticationState to re-read the database when claims have changed?

I've implemented a ServerAuthenticationStateProvider and it works great for my adding IdentityUser.Enabled. But when I set it to re-read the claims on a call to ExAuthenticationStateProvider.GetAuthenticationStateAsync(), while that does return and updated list of the claims, that list is not propagated to the AuthenticationState .

Is there a way to propigate it other than having the user log out and back in?

My Routes.razor is:

<Router AppAssembly="typeof(Program).Assembly">
    <Found Context="routeData">
        <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
            <NotAuthorized>
                <div class="message--401">
                    <h1 role="alert">Sorry, you're not authorized to view this page.</h1>
                    <p>You must have an Admin claim and be set to use 2FA when logging in.</p>
                </div>
            </NotAuthorized>
        </AuthorizeRouteView>
        <FocusOnNavigate RouteData="@routeData" Selector="h1" />
    </Found>
    <NotFound>
        <PageTitle>Not found</PageTitle>
        <LayoutView Layout="@typeof(MainLayout)">
            <div class="message--404">
                <h1 role="alert">Sorry, there's nothing at this address.</h1>
            </div>
        </LayoutView>
    </NotFound>

Solution

  • You could override the GetAuthenticationStateAsync() method in your CustomServerAuthenticationStateProvider class to return new idenity information from the source your need. (such as db)
    For example, we could get the user information using userManager.GetUserAsync from database and returned.

        public class CustomServerAuthenticationStateProvider : ServerAuthenticationStateProvider
        {
            private readonly UserManager<ApplicationUser> userManager;
            private readonly SignInManager<ApplicationUser> signInManager;
            public CustomServerAuthenticationStateProvider(UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager)
            {
                this.userManager = userManager;
                this.signInManager = signInManager;
            }
            public override async Task<AuthenticationState> GetAuthenticationStateAsync()
            {
                var authenticationState =await base.GetAuthenticationStateAsync(); //use method from base to get current state.
                var user = await userManager.GetUserAsync(authenticationState.User);
                var claimsPrincipal =await signInManager.CreateUserPrincipalAsync(user);
                return new AuthenticationState(claimsPrincipal);
            }
            public async Task RefreshAsync()
            {
                NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
            }
        }
    

    Then GetAuthenticationStateAsync will always return you the updated claims. If you want to refresh the state you could inject custom provider and call _customProvider.RereshAsync() method. You could register service like below pattern:

    builder.Services.AddScoped<CustomServerAuthenticationStateProvider>();
    builder.Services.AddScoped<AuthenticationStateProvider>(sp=> sp.GetRequiredService<CustomServerAuthenticationStateProvider>());