OpenID Connect Back-Channel Logout

Avanzado
Spring Security
Spring Security
Actualizado: 19/04/2026

Cuando un usuario cierra sesión en su OpenID Provider (Google, Keycloak, Entra ID), las aplicaciones que dependen de él deberían enterarse y cerrar también su sesión local. Sin esa propagación, una persona puede creer que ha hecho logout cuando en realidad sigue autenticada en una decena de portales internos.

OpenID Connect Back-Channel Logout (especificación de 2022) resuelve este problema con un flujo server-to-server: el provider notifica directamente al backend de cada Relying Party sin pasar por el navegador.

Diferencia con Front-Channel Logout

OIDC define dos especificaciones de logout:

  • Front-Channel Logout: el provider devuelve una página con <iframe> a cada RP. El navegador del usuario carga esos iframes y cada RP destruye la cookie del lado cliente. Depende de cookies de terceros, cada vez más bloqueadas por los navegadores.
  • Back-Channel Logout: el provider hace un POST directo a un endpoint de cada RP con un logout_token JWT. No depende de cookies ni del navegador. Es la opción moderna y recomendada.
sequenceDiagram
    participant U as Usuario
    participant OP as OpenID Provider
    participant RP1 as App 1 (RP)
    participant RP2 as App 2 (RP)
    U->>OP: GET /logout
    OP->>OP: Identificar sesiones afectadas
    par Notificar RPs
        OP->>RP1: POST /logout/connect/back-channel/{id}<br>logout_token=eyJ...
        OP->>RP2: POST /logout/connect/back-channel/{id}<br>logout_token=eyJ...
    end
    RP1->>RP1: Invalidar SecurityContext
    RP2->>RP2: Invalidar SecurityContext
    OP->>U: Logout confirmado

Back-Channel Logout es completamente invisible para el usuario. Cuando vuelva a entrar a cualquier RP, será redirigido al login del OP.

El logout_token

El logout_token es un JWT firmado por el OP con un conjunto reducido de claims:

{
  "iss": "https://auth.demo.local",
  "aud": "spring-rp",
  "iat": 1736006400,
  "jti": "logout-12345",
  "events": {
    "http://schemas.openid.net/event/backchannel-logout": {}
  },
  "sub": "user-789",
  "sid": "session-abc"
}
  • events debe contener exactamente la URL http://schemas.openid.net/event/backchannel-logout.
  • sub o sid (al menos uno) identifican qué sesión cerrar.
  • nonce está prohibido (a diferencia del id_token).

Activar Back-Channel Logout en Spring

Spring Security 7 soporta back-channel logout out-of-the-box. La configuración mínima es declarativa.

@Bean
SecurityFilterChain http(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/login/**", "/logout/**").permitAll()
            .anyRequest().authenticated())
        .oauth2Login(Customizer.withDefaults())
        .oidcLogout(oidc -> oidc.backChannel(Customizer.withDefaults()));
    return http.build();
}

oidcLogout().backChannel() registra el OidcBackChannelLogoutFilter que escucha en /logout/connect/back-channel/{registrationId}.

spring:
  security:
    oauth2:
      client:
        registration:
          empresa:
            client-id: spring-rp
            client-secret: ${RP_SECRET}
            scope: openid, profile
            provider: empresa-auth
        provider:
          empresa-auth:
            issuer-uri: "https://auth.demo.local"

El provider debe configurarse con la URL de back-channel del cliente, p. ej. https://app.demo.local/logout/connect/back-channel/empresa.

Validación del logout_token

Spring valida automáticamente:

  • Firma: con el JWK Set del OP.
  • iss: debe coincidir con el issuer-uri configurado.
  • aud: debe contener el client_id del RP.
  • iat: presente y no demasiado antiguo.
  • events: presente con la clave canónica.
  • sub o sid: al menos uno.
  • nonce: ausente (su presencia rechaza el token).

Si cualquier validación falla, devuelve HTTP 400 Bad Request y no toca el SecurityContext.

Mapeo de logout_token a sesión local

Por defecto, Spring busca todas las sesiones HTTP del usuario y las invalida. Esto funciona bien con HttpSession clásica.

@Bean
SessionRegistry sessionRegistry() {
    return new SessionRegistryImpl();
}

Para Spring Session con Redis (caso típico en producción distribuida):

<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>
spring:
  session:
    store-type: redis
    redis:
      namespace: rp:session

Spring Session gestiona el SessionRegistry distribuido y la invalidación se propaga a todos los nodos automáticamente.

Sesión por usuario, sesión por sid

Si el OP envía el sid, lo correcto es invalidar solo esa sesión, no todas las del usuario. Esto permite que un usuario con varias pestañas o dispositivos cierre solo una.

Spring asocia el sid a la sesión local en el momento del login y lo guarda como atributo. Cuando llega un back-channel logout con sid, busca y mata solo esa.

HttpSession httpSession = request.getSession(false);
if (httpSession != null) {
    String storedSid = (String) httpSession.getAttribute("oidc.sid");
    if (storedSid.equals(claimSid)) {
        httpSession.invalidate();
    }
}

Logout iniciado por el RP (RP-Initiated Logout)

Complementario al back-channel: el usuario clica "Cerrar sesión" en la app, esta destruye su sesión local y redirige al end_session_endpoint del OP para que también cierre la sesión central.

.oauth2Login(Customizer.withDefaults())
.logout(logout -> logout
    .logoutUrl("/auth/logout")
    .logoutSuccessHandler(oidcLogoutSuccessHandler()))
@Bean
LogoutSuccessHandler oidcLogoutSuccessHandler() {
    OidcClientInitiatedLogoutSuccessHandler handler =
        new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository);
    handler.setPostLogoutRedirectUri("{baseUrl}/");
    return handler;
}

Combinar RP-Initiated y Back-Channel da la mejor experiencia: el usuario puede iniciar logout desde cualquier app y el resto de RPs se enteran sin acción del usuario.

Configuración en proveedores comunes

Keycloak

En la sección Settings del cliente, añade https://app.demo.local/logout/connect/back-channel/empresa en Backchannel Logout URL y activa Backchannel Logout Session Required.

Entra ID

En App registrations, Token configuration, añade el atributo opcional sid al ID Token. En Authentication, configura Front-channel logout URL o usa el endpoint de Microsoft Graph para gestionar back-channel.

Auth0

En la pestaña Endpoints del tenant, añade https://app.demo.local/logout/connect/back-channel/empresa en Back-Channel Logout URLs.

Auditoría y observabilidad

Cada back-channel logout debería loguearse con el jti (para detectar replays), el sub y el resultado (éxito o fallo).

@EventListener
public void onLogout(LogoutSuccessEvent event) {
    AUDIT.info("BACK_CHANNEL_LOGOUT user={}", event.getAuthentication().getName());
}

Métrica útil para Grafana: oidc_back_channel_logout_total{result="success|failure"}.

Errores frecuentes

  • No exponer la URL públicamente: el OP debe poder hacer POST al endpoint. Si está detrás de un firewall interno, el back-channel no llega.
  • Aceptar nonce en el logout_token: lo prohíbe la especificación; un token con nonce debe rechazarse.
  • Olvidar HTTPS: el back-channel debe ir sobre TLS. En desarrollo conviene usar mkcert o un dominio real.
  • No persistir el sid: sin él, no puedes invalidar selectivamente y cualquier logout cierra todas las sesiones del usuario.
Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Spring Security es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Spring Security

Explora más contenido relacionado con Spring Security y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Implementar OpenID Connect Back-Channel Logout en Spring Security 7, validar el logout token JWT enviado por el provider y propagar la invalidación a sesiones distribuidas.