This post shows how to implement an OpenID Connect back-channel logout using Keycloak, ASP.NET Core and .NET Aspire. The Keycloak and the Redis cache are run as containers using .NET Aspire. Two ASP.NET Core UI applications are used to demonstrate the server logout.
Code: https://github.com/damienbod/keycloak-backchannel
Setup
The applications are run and tested using .NET Aspire. The UI applications are setup using the OpenID Connect code flow with PKCE and OAuth PAR. Two docker containers are used, one for the Redis cache and one for the Keycloak server.

.NET Aspire Setup
The AppHost project in .NET Aspire is used to configure the different services. The Keycloak container is run using HTTPS with develop certificates. The ASP.NET Core applications are run using development certificates as well. For the Keycloak server to access the docker host, it must register the same developer certificates from the host, or disable the certificate trust manger inside the container. This works good, but should only be setup like this in development. The different project must reference each other as required. To use Redis and Keycloak, the Aspire Nuget packages for these containers need to be installed.
var keycloak = builder.AddKeycloakContainer("keycloak",
userName: userName, password: password, port: 8080)
.WithArgs("--features=preview")
// for more details regarding disable-trust-manager see https://www.keycloak.org/server/outgoinghttp#_client_configuration_command
// IMPORTANT: use this command ONLY in local development environment!
.WithArgs("--spi-connections-http-client-default-disable-trust-manager=true")
.WithDataVolume()
.RunWithHttpsDevCertificate(port: 8081);
var cache = builder.AddRedis("cache", 6379)
.WithDataVolume();
var mvcpar = builder.AddProject<Projects.MvcPar>("mvcpar")
.WithExternalHttpEndpoints()
.WithReference(keycloak)
.WithReference(cache);
var mvcbackchanneltwo = builder.AddProject<Projects.MvcBackChannelTwo>("mvcbackchanneltwo")
.WithExternalHttpEndpoints()
.WithReference(keycloak)
.WithReference(cache);
Keycloak OpenID Connect client configuration
The Keycloak client should have the backchannel logout activated. The container uses the localhost applications from the docker host and so the host.docker.internal domain is used. The logout endpoint is implemented in the ASP.NET Core application.

ASP.NET Core Logout
Each ASP.NET Core application that supports the back-channel logout must have a server implementation and provide a web hook for the identity provider (Keycloak) logout event. If multiple instances are running, one can send a logout event to Keycloak. This ends the session on the identity provider and sends a logout post request to all server backends hosting the UI application. The logout event is handled and persisted to a distributed cache. For all other instances which request data from the server for the same user and session, the session is ended and the user must authentication again.
- Logout request
- Webhook for logout event from Keycloak server for all instances
- Persist event to cache if missing and logout
- Logout UI on next HTTP request for user sessions
services.AddTransient<CookieEventHandler>();
services.AddSingleton<LogoutSessionManager>();
services.AddHttpClient();
services.Configure<AuthConfiguration>(configuration.GetSection("AuthConfiguration"));
var authConfiguration = configuration.GetSection("AuthConfiguration");
builder.AddRedisDistributedCache("cache");
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
options.Cookie.Name = "MvcPar";
options.EventsType = typeof(CookieEventHandler);
})
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.Authority = authConfiguration["StsServerIdentityUrl"];
options.ClientSecret = authConfiguration["ClientSecret"];
options.ClientId = authConfiguration["Audience"];
options.ResponseType = OpenIdConnectResponseType.Code;
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.Scope.Add("offline_access");
options.ClaimActions.Remove("amr");
options.ClaimActions.MapJsonKey("website", "website");
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.PushedAuthorizationBehavior = PushedAuthorizationBehavior.Require;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role,
};
});
Note: The sample code in the repository was created using the IdentityServer4 Samples.
Redis cache
Redis Insight can be used to view the Redis cache data. Each time the application handles a new user and session logout event, it persists the event to the cache. If any further application instances are authenticated using this session and user, the application will sign-out as well on the next server event. The expiry time of the cache should be long enough so that an offline session cannot be opened after it expires.

Limitations
The back-channel logout only works on a per browser session because Keycloak creates new sessions for each browser. When the logout is received it is filtered and handled using the Keycloak session. If Keycloak can use a single session for all browsers of each user, then the logout can work for all active UI apps of the same user.
Links
https://openid.net/specs/openid-connect-backchannel-1_0.html
https://ldapwiki.com/wiki/OpenID%20Connect%20Back-Channel%20Logout
https://datatracker.ietf.org/meeting/97/materials/slides-97-secevent-oidc-logout-01
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/app-state
https://docs.microsoft.com/en-us/azure/azure-cache-for-redis/cache-dotnet-core-quickstart