Search code examples
asp.net-coreazure-active-directoryblazor-server-side

Blazor with OIDC (AAD), how to log out?


I have Blazor server app on .NET 7, using Azure AD authentication, with typical "tutorial" setup:

services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(options =>
    {        
        configuration.Bind(configSectionName, options);        
    })
    .EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
    .AddMicrosoftGraph(configuration.GetSection("MicrosoftGraph"))
    .AddInMemoryTokenCaches();

It works fine. But how do I correctly sign out user?

If I use <a href="MicrosoftIdentity/Account/SignOut">Log out</a> and click it, it will just navigate to /MicrosoftIdentity/Account/SignOut and say Sorry, there's nothing at this address.

Isn't the AddMicrosoftIdentityWebApp call supposed to setup some handler for these URLs and handle sign-out correctly (call logout endpoint on AAD, etc)? Am I missing some configuration in order for this to happen?

EDIT: My configuration section from appsettings.json

"AzureAd": {
  "Instance": "https://login.microsoftonline.com/",
  "Domain": "mydomain.com",
  "TenantId": "263639d4-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "ClientId": "06176f4f-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "ClientSecret": "rSAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  "CallbackPath": "/signin-oidc"
}

If I open chrome developer tools, normally when I click on links, nothing appears in network log (which I understand to be normal, websocket connection is used), but when I click sign out link, there seems to be full page load - HTML, css, javascripts, websocket connect...


Solution

  • After some research, here is what I found.

    With my configuration, it is expected for /MicrosoftIdentity/Account/SignOut endpoint to NOT exists. These endpoints are handled by AccountController (source) which is added by Microsoft.Identity.Web.UI which I don't have (or need - my app has no user management UI; every user in AAD tenant can access it, and noone else).

    So I looked into source code of components implementing sign out for OIDC (mainly RemoteAuthenticationHandler and OpenIdConnectHandler) and this is basically how you can do Microsoft AD sign out in Blazor server without all the UI overhead:

    @using System.Security.Claims;
    @inject IHttpContextAccessor httpContextAccessor
    
    <AuthorizeView>
        <Authorized>
            @context.User.Identity?.Name
            <a href="@logoutRedirectUri"><span class="oi oi-account-logout" title="Log out"></span></a>
        </Authorized>    
    </AuthorizeView>
    
    @code {
        string logoutRedirectUri = string.Empty;
    
        protected override void OnInitialized()
        {
            var request = httpContextAccessor.HttpContext.Request;
    
            var loginHint = request.HttpContext.User.FindFirstValue("login_hint");
    
            logoutRedirectUri = "https://login.microsoftonline.com/common/oauth2/v2.0/logout?post_logout_redirect_uri="
                + Uri.EscapeDataString(request.Scheme + Uri.SchemeDelimiter + request.Host + request.PathBase + "/signout-oidc");
    
            if (loginHint != null)
            {
                logoutRedirectUri += "&logout_hint=" + Uri.EscapeDataString(loginHint);
            }
        }
    }
    

    You need to add "SignedOutCallbackPath": "/signout-oidc" to your configuration (right along with "CallbackPath": "/signin-oidc") AND you need to add this URL to redirect URLs in your app registration in Azure portal (where you have /signin-oidc URLs).

    The logout_hint is optional and what it does is, that in case you are logged into into multiple MS accounts, logout_hint can skip the step where you must pick the one you want to logout from. See here.