What is the preferred way to log-out a ClaimsIdentity (IsAuthenticated be false) in the eyes of Razor <AuthorizeView> tag?

55 views Asked by At

I am building a simple custom authentication method in my Blazor app. The several pages must be visible by a logged-in user through the Razor <AuthorizeView> attribute.

I am able to make it work by adding to the ClaimsPrincipal (obtained through the cascading AuthenticationState) a new ClaimsIdentity with a non-null authentication type (which is the info the AuthorizeView attribute uses when no Role or Policy is specified, see Why is my ClaimsIdentity IsAuthenticated always false (for web api Authorize filter)?). But then I am not able to remove that "authenticated" ClaimsIdentity when I want to "log-out" the user.

The workaround I have found is to plug a role called "loggedin" into the principal (through a new ClaimsIdentity) when he logs in, and unplug the role from the related ClaimsIdentity when he logs out. The Razor attribute controlling the visibility is then <AuthorizeView Role="loggedin">. I could also use a Policy, but both seem dirty to me, since here I only want the pages to be visible when the user is authenticated (instead of being "authorized", which roles and policies are made for, and I find is a concept that should be distincted from mere authentication). But maybe it's the right thing to do, I don't know.

So, what is the cleanest way to specify that a page should be visible only to authenticated users (without further conditions), and control the authentication state of the user, only using the Razor AuthorizeView attribute (and children), ClaimsPrincipal and ClaimsIdentity ?

1

There are 1 answers

5
MrC aka Shaun Curtis On

As you're question is light on code, here's a demo on how to do what I think you want. This is based on Blazor Server.

It uses a custom AuthenticationStateProvider to capture the original user and store the claims. You can then construct your own ClaimsIdentity and ClaimsPrincipal using the captured claims. If you principals have more that one identity you will need to code to capture that.

A custom authentication State Provider:

public class MyAuthenticationProvider : ServerAuthenticationStateProvider
{
    ClaimsPrincipal? _user;
    IEnumerable<Claim>? _claims;

    public async override Task<AuthenticationState> GetAuthenticationStateAsync()
    {
        var authState = await base.GetAuthenticationStateAsync();

        var user = authState.User;
        if (user.Identity?.IsAuthenticated ?? false)
            _claims = user.Identities.FirstOrDefault()?.Claims ?? Enumerable.Empty<Claim>();

        return authState;
    }

    public Task LogOutIdentityAsync()
    {
        _user = new ClaimsPrincipal(new ClaimsIdentity(_claims, null));
        var newState = new AuthenticationState(_user);
        this.NotifyAuthenticationStateChanged(Task.FromResult<AuthenticationState>(newState));
        return Task.CompletedTask;
    }

    public Task LogInIdentityAsync()
    {
        _user = new ClaimsPrincipal(new ClaimsIdentity(_claims, "nlm"));
        var newState = new AuthenticationState(_user);
        this.NotifyAuthenticationStateChanged(Task.FromResult<AuthenticationState>(newState));
        return Task.CompletedTask;
    }
}

Registered:

// at the end of services definitions
builder.Services.AddScoped<AuthenticationStateProvider, MyAuthenticationProvider>();

A demo Layout to toggle the authentication.

@inherits LayoutComponentBase
@inject AuthenticationStateProvider _authProvider

<PageTitle>BlazorApp1</PageTitle>

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <main>
        <div class="top-row px-4 auth">
            <button class="btn btn-primary" @onclick="ToggleAuth">Toggle</button>
            <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
        </div>

        <article class="content px-4">
            @Body
        </article>
    </main>
</div>

@code {
    bool _authState = true;

    void ToggleAuth()
    {
        if (_authState)
            ((MyAuthenticationProvider)_authProvider).LogOutIdentityAsync();
        else
            ((MyAuthenticationProvider)_authProvider).LogInIdentityAsync();

        _authState = !_authState;
    }
}