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...
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.