have a Blazor Server application configured with OpenID Connect (OIDC) authentication using Keycloak. Login works fine and I can authenticate successfully. However, when I log out, I get redirected to the Keycloak logout page but upon returning to my Blazor application, I find that I’m still logged in, even though the Keycloak session is expired.
Here are the details of my setup:
Program.cs
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddHttpContextAccessor();
builder.Services.AddSingleton<LogoutService>();
// Configure logging
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
// Configure OIDC authentication
var oidcSettings = builder.Configuration.GetSection("OIDC");
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.Events.OnSigningOut = async e =>
{
e.HttpContext.Response.Cookies.Delete(".AspNetCore.Cookies");
await Task.CompletedTask;
};
})
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Scope.Add(OpenIdConnectScope.OfflineAccess);
options.MetadataAddress = "https://keycloak.example.com/realms/myrealm/.well-known/openid-configuration";
options.Authority = oidcSettings["Authority"];
options.ClientId = oidcSettings["ClientId"];
options.ClientSecret = oidcSettings["ClientSecret"];
options.ResponseType = OpenIdConnectResponseType.Code;
options.SaveTokens = false;
options.GetClaimsFromUserInfoEndpoint = true;
options.CallbackPath = new PathString("/signin-oidc");
options.SignedOutCallbackPath = new PathString("/signout-callback-oidc");
options.RemoteSignOutPath = new PathString("/signout-oidc");
options.SignOutScheme = OpenIdConnectDefaults.AuthenticationScheme;
options.MapInboundClaims = false;
options.TokenValidationParameters.NameClaimType = JwtRegisteredClaimNames.Name;
options.TokenValidationParameters.RoleClaimType = "role";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = oidcSettings["Authority"],
ValidateAudience = true,
ValidAudience = oidcSettings["ClientId"],
ValidateLifetime = true
};
options.Scope.Add("profile");
options.Scope.Add("email");
});
builder.Services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.AddPolicy("AnonymousPolicy", policy => policy.RequireAssertion(_ => true));
});
builder.Services.AddControllersWithViews();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft": "Debug",
"Microsoft.AspNetCore": "Debug"
}
},
"OIDC": {
"Authority": "https://keycloak.example.com/realms/myrealm",
"ClientId": "myclientid",
"ClientSecret": "myclientsecret",
"CallbackPath": "/signin-oidc",
"PostLogoutRedirectUri": "https://localhost:7029/",
"SignedOutCallbackPath": "/signout-callback-oidc",
"SignOutPath": "/signout-oidc"
}
}
Razor Page
@page "/counter"
@using Microsoft.AspNetCore.Authentication.Cookies
@using Microsoft.AspNetCore.Authentication.OpenIdConnect
<PageTitle>Index</PageTitle>
@inject NavigationManager Navigation
@inject IConfiguration Configuration
@inject IHttpContextAccessor HttpContextAccessor
<AuthorizeView>
<Authorized>
<button @onclick="Logout">Logout</button>
</Authorized>
<NotAuthorized>
<button @onclick="Login">Login</button>
</NotAuthorized>
</AuthorizeView>
@code {
private void Login()
{
Navigation.NavigateTo("https://keycloak.example.com/realms/myrealm/protocol/openid-connect/auth?client_id=myclientid&redirect_uri=https://localhost:7029/signin-oidc&response_type=code&scope=openid");
}
private void Logout()
{
var authority = Configuration["OIDC:Authority"];
var postLogoutRedirectUri = Configuration["OIDC:PostLogoutRedirectUri"];
var logoutUrl = $"{authority}/protocol/openid-connect/logout?client_id=myclientid&post_logout_redirect_uri={Navigation.ToAbsoluteUri("/")}";
Navigation.NavigateTo(logoutUrl, true);
}
}
Problem Description:
Login works and redirects successfully to the application.
Logging out redirects to the Keycloak logout page, but when I return to the application, I’m still logged in.
The session in Keycloak is expired but the Blazor application still recognizes the user as logged in.
mohamed moussa is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.