I know my following question is very related to this one: existing question but the answer is not solving my problem and maybe there is something else out there.
So I have a blazor web app in .net 8 using server side rendering. I want to make SSO login/logout using Azure Entra Id(active directory) with OpenIdConnect.
Sign it works fine, but the with sign out I have more problems. How can I do it without
calling a redirect to Navigation.NavigateTo("MicrosoftIdentity/Account/SignOut", true);
. I do not want to use Microsoft.Identity.Web.UI
because I do not want to create pages in my app if is not necessary. I do not manage the users from the app.
I tried to logout using something like this:
NavigationManager.NavigateTo("https://login.microsoftonline.com/{tenantid}/oauth2/logout?post_logout_redirect_uri=https://localhost:7216/signout-oidc");
or
var request = httpContextAccessor.HttpContext.Request;
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");
NavigationManager.NavigateTo(logoutRedirectUri, true);
I am redirected to https://localhost:7216/signout-oidc
which is a blank page. If I try to redirect it "/"(home page) than I am automatically logged in.
This is what I have configured and also the links are registered in app registration in Azure portal
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "{clientId}",
"TenantId": "{tenantid}",
"CallbackPath": "/signin-oidc",
"SignedOutCallbackPath": "/signout-oidc"
}
Edit - Complete code:
Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMudServices(config =>
{
config.SnackbarConfiguration.PositionClass = Defaults.Classes.Position.TopRight;
config.SnackbarConfiguration.PreventDuplicates = false;
config.SnackbarConfiguration.NewestOnTop = true;
config.SnackbarConfiguration.ShowCloseIcon = true;
config.SnackbarConfiguration.VisibleStateDuration = 7000;
config.SnackbarConfiguration.HideTransitionDuration = 500;
config.SnackbarConfiguration.ShowTransitionDuration = 500;
config.SnackbarConfiguration.SnackbarVariant = Variant.Outlined;
});
builder.Services.AddDbContextFactory<Context>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));
});
//Looger
builder.Logging.AddAzureWebAppDiagnostics();
builder.Logging.AddApplicationInsights(
configureTelemetryConfiguration: (config) =>
config.ConnectionString = builder.Configuration.GetConnectionString("AppInsights"),
configureApplicationInsightsLoggerOptions: (options) => { }
);
//SSO Login
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(options =>
{
// Bind the configuration section and set additional options
builder.Configuration.Bind("AzureAd", options);
});
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));
options.AddPolicy("UserOnly", policy => policy.RequireRole("User"));
});
builder.Services.AddScoped<NotificationToastUtility>();
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
Registration.AddRegistration(builder.Services);
// Add SignalR services
builder.Services.AddSignalR();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAntiforgery();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
app.MapRazorPages();
app.MapControllers();
// Map the SignalR hub
app.MapHub<StepHub>("/stephub");
app.Run();
In the navbar I have a link that calls the logout
<a href="@logoutRedirectUri"><span class="oi oi-account-logout">Logout</span></a>
Code behind where @logoutRedirectUri is created
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);
}
}
There are two levels of logging out:
Log out from your application. This is usually done clearing authentication cookies
Logging out from the SSO provider (Microsoft Entra ID) which will log you out from all apps. This is done by redirecting to https://login.microsoftonline.com/common/oauth2/v2.0/logout.
I do not want to use Microsoft.Identity.Web.UI because I do not want to create pages in my app if is not necessary. I do not manage the users from the app.
The Microsoft.Identity.Web.UI
package is not about managing users, it's about managing the authentication state.
For example, clearing authentication cookie in your app or showing a logout page.
If you don't have logout page with anonymous access you are automatically redirected to login page where you might be automatically logged in again.
you should check the source code: https://github.com/AzureAD/microsoft-identity-web/tree/master/src/Microsoft.Identity.Web.UI/Areas/MicrosoftIdentity
If you don't want to use Microsoft.Identity.Web.UI
, you will have to create your own page/controller to handle it. See the /MicrosoftIdentity/Account/SignOut
action:
/// <summary>
/// Handles the user sign-out.
/// </summary>
/// <param name="scheme">Authentication scheme.</param>
/// <returns>Sign out result.</returns>
[HttpGet("{scheme?}")]
public IActionResult SignOut(
[FromRoute] string scheme)
{
if (AppServicesAuthenticationInformation.IsAppServicesAadAuthenticationEnabled)
{
if (AppServicesAuthenticationInformation.LogoutUrl != null)
{
return LocalRedirect(AppServicesAuthenticationInformation.LogoutUrl);
}
return Ok();
}
else
{
scheme ??= OpenIdConnectDefaults.AuthenticationScheme;
var callbackUrl = Url.Page("/Account/SignedOut", pageHandler: null, values: null, protocol: Request.Scheme);
return SignOut(
new AuthenticationProperties
{
RedirectUri = callbackUrl,
},
CookieAuthenticationDefaults.AuthenticationScheme,
scheme);
}
}
The return SignOut()
response does the magic here, but you could manually clear cookies and potentionally redirect to SSO logout page