Ivan-Mark Debono
Ivan-Mark Debono

Reputation: 16280

How to sign out successfully from a Blazor server-side app?

I'm logging out by doing the following:

public class LogoutModel : PageModel
{
    public async Task OnGet(string redirectUri)
    {
        await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties
        {
            RedirectUri = redirectUri,
        });
    }
}

signing out should be working because I get redirected to the correct page in which I have a link to go back to the home page:

<p class="mb-1">
    @{
        var url = $"{navigationManager.BaseUri}";

        <NavLink class="nav-link" href=@($"{url}") Match="NavLinkMatch.Prefix">
            Login again
        </NavLink>
    }
</p> 

The razor page is as follows:

<AuthorizeView>
    <Authorized>
        @{
            navigationManager.NavigateTo("Main", forceLoad: true);
        }
    </Authorized>
    <NotAuthorized>
        @{
            var returnUrl = $"{navigationManager.BaseUri}{options.LoginCallbackUrl}";
            navigationManager.NavigateTo($"Login?redirectUri={returnUrl}", forceLoad: true);
        }
    </NotAuthorized>
 </AuthorizeView>

At this stage, the code within the Authorized tag gets executed and I get redirected immediately to the main page. Which should not happen.

The OpenId settings in Startup.cs are:

services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})

Why is the app still thinking that the user is still authorized? Am I missing a step?

Upvotes: 1

Views: 5470

Answers (3)

Ivan-Mark Debono
Ivan-Mark Debono

Reputation: 16280

There is no need to subclass any classes. The way to solve the problem is by first doing:

public class LogoutModel : PageModel
{
    public async Task OnGet(string redirectUri)
    {
        await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
        await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties
        {
            RedirectUri = redirectUri,
        });
    }
}

This will sign out the user from cookies and OpenId. To navigate here, one can do the following in a razor component:

@{
    var returnUrl = $"{navigationManager.BaseUri}{LogoutCallbackUrl}";
    <NavLink class="nav-link" href=@($"Logout?redirectUri={returnUrl}") Match="NavLinkMatch.Prefix" />
}

where LogoutCallbackUrl will be the page for the redirect was sign out has completed.

The last step is to make sure that the logout callback redirect page allows for anonymous access. So if the callback is a razor component, this would be done by adding the following:

@attribute [Microsoft.AspNetCore.Authorization.AllowAnonymous]

Upvotes: 2

Remi THOMAS
Remi THOMAS

Reputation: 910

Keep in mind Blazor server side is SPA application, this mean the page is never reload, DOM is update with the server <-> SignalR <-> Javascript tunnel. This mean cookie authentication is only possible is you force page reload.

To understand how to handle authentication, including sign out, have a look at this example. https://github.com/iso8859/AspNetCoreAuthMultiLang

In AuthenticationStateProvider.GetAuthenticationStateAsync implementation you must return empty token

new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); = not authenticated.

Upvotes: 1

Baskovli
Baskovli

Reputation: 640

You have to get authentication state on main layout - lifecycle event OnInitialized{Async}.

protected override async Task OnInitializedAsync()
{
    var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
    User = authState.User;
    if (!User.Identity.IsAuthenticated)
    {
        NavMan.NavigateTo("Identity/Account/Login", true);
    }
}

Upvotes: 2

Related Questions