Blazor Server Side - How to use RevalidatingServerAuthenticationStateProvider to keep checking token expiry?
Asked Answered
A

2

7

I am trying to create a RevalidatingServerAuthenticationStateProvider to check if a ClaimsPrincipal has expired.

I have created an implementation of it (TokenExpiryAuthStateProvider) and registered it in ConfigureServices but the constructor is never called nor is ValidateAuthenticationStateAsync. (I also tried the implementation factory to register it, same result)

I injected AuthStateProvider to MainLayout to see what got injected and it is still injecting ServerAuthenticationStateProvider. What am I doing wrong?

My implementation that is never called:

public class TokenExpiryAuthStateProvider : RevalidatingServerAuthenticationStateProvider
{
    protected override TimeSpan RevalidationInterval => TimeSpan.FromSeconds(10);

    public TokenExpiryAuthStateProvider(ILoggerFactory logger) : base(logger)
    {

    }

    protected override Task<bool> ValidateAuthenticationStateAsync(AuthenticationState authenticationState, CancellationToken cancellationToken)
    {
        DateTime expiry = authenticationState.User.GetExpiryDate();

        if (expiry < DateTime.UtcNow)
            return Task.FromResult(false);
        else
            return Task.FromResult(true);
    }
}

Configure Services

public void ConfigureServices(IServiceCollection services)
    {
        services.AddScoped<HttpClientFactory>();

        services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();
        services.ConfigureApplicationCookie(options =>
        {
            options.Cookie.HttpOnly = true;
            options.SlidingExpiration = true;
            options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
        });

        services.AddScoped<AuthenticationStateProvider, TokenExpiryAuthStateProvider>();

        services.AddRazorPages();
        services.AddServerSideBlazor();
        services.AddHttpContextAccessor();

        services.AddCsla().WithBlazorServerSupport();            
    }

The injected object:

The injected object

UPDATE: Should have mentioned this before...I am currently working on a server-side project but the layouts are in a shared ui library. The suggestion made by @enet therefore isn't suitable as RevalidatingServerAuthenticationStateProvider will not be available in the shared project and therefore TokenExpiryAuthStateProvider is only in the server project.

Still, following on from the suggestion by enet I declared an interface and updated ConfigureServices so that I could inject it in my shared layout to try it out:

services.AddScoped<TokenExpiryAuthStateProvider>();
services.AddScoped<ITokenExpiryProvider>(provider => 
        provider.GetRequiredService<TokenExpiryAuthStateProvider>());

I then injected an ITokenExpiryProvider to MainLayout, and subscribed to the AuthenticationStateChanged event (The only event I could see). After the refresh interval, no state changed has been fired and ValidateAuthenticationStateAsync has not been called. How do I start the Revalidating process and why does AddScoped<AuthenticationStateProvider, TokenExpiryAuthStateProvider> not work?

I think for my simple use case it will be easier to create a timer class which is injected with AuthenticationStateProvider; this class then encapsulates the periodic checking of the auth state and in turn raises an event when the session is expiring. (This is how I was expecting RevalidatingServerAuthenticationStateProvider to work, checking ValidateAuthenticationStateAsync whenever RefreshInterval had elapsed)

I would still like to know why my implementation isn't working and why ValidateAuthenticationStateAsync is never being called; I must be registering the service incorrectly somehow.

Alvinalvina answered 25/9, 2020 at 14:39 Comment(0)
S
8

I had this same issue in a Blazor Server app when implementing my own RevalidatingServerAuthenticationStateProvider. I found that this problem was the order of how everything is set up in the Startup.cs.

By calling services.AddRazorPages() and services.AddServerSideBlazor() before I setup my authentication providers I corrected the issue, and now my ValidationAuthenticationStateAsync() method gets called at the specified RevalidationInterval.

Here is an abbreviated version of the ConfigureServices(...) method in my Startup.cs as an example:

public void ConfigureServices(IServiceCollection services)
{
    // These two need to be called before adding authentication
    services.AddRazorPages();
    services.AddServerSideBlazor();

    services.AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddCookie()
    .AddOpenIdConnect();

    // This needs to be setup after setting up Blazor
    services.AddScoped<AuthenticationStateProvider, CustomRevalidatingAuthenticationStateProvider>();
}
Schwartz answered 8/12, 2020 at 23:7 Comment(0)
H
1

As MkChandler said I was investigating why on server side the AuthenticationStateProvider is converted to ServerAuthenticationStateProvider which was causing the error.

After finding this post I moved the services.AddServerSideBlazor(); before services.AddScoped<AuthenticationStateProvider, ApiAuthenticationStateProvider>(); and it worked.

Note: I am trying to convert a whole blazor wasm project to blazor server side when this problem occurred.

Haruspicy answered 30/8, 2021 at 17:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.