Skip to content

Conversation

jzheaux
Copy link
Contributor

@jzheaux jzheaux commented Aug 8, 2024

This brings support for #13841 and #14904.

To configure OIDC Back-Channel logout to reuse the Back-Channel endpoint to invalidate each individual session (thus removing the need for the CSRF token), configure OIDC logout in the following way:

.oidcLogout((oidc) -> oidc
    .backChannel(Customizer.withDefaults())
)

NOTE that for simplicity, this changes the default internal URI. Since it changes it to point back to the OIDC back-channel URL itself, I believe this will go unnoticed by most applications. That said, in case you need your URI to stay as-is, you can specify the URI as follows:

// ...
.oidcLogout((oidc) -> oidc
    .backChannel((backChannel) -> backChannel
        .logoutUri(myCustomInternalUri)
    )
)

To configure OIDC Back-Channel Logout with a different cookie name, configure it as follows:

@Bean 
OidcSessionRegistry sessionRegistry() {
    return new InMemoryOidcSessionRegistry();
}

@Bean
OidcBackChannelLogoutHandler oidcLogoutHandler(OidcSessionRegistry sessionRegistry) {
    OidcBackChannelLogoutHandler logoutHandler = new OidcBackChannelLogoutHandler(sessionRegistry);
    // the logout URI can also be configured here
    logoutHandler.setSessionCookieName("SESSION");
    return logoutHandler;
}

Or it can also be specified in the DSL directly:

// ...
.oidcLogout((oidc) -> oidc
    .backChannel((backChannel) -> backChannel
        .logoutHandler(logoutHandler)
    )
)

@jzheaux jzheaux added type: enhancement A general enhancement in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) labels Aug 8, 2024
@jzheaux jzheaux added this to the 6.4.0-M2 milestone Aug 8, 2024
@jzheaux jzheaux self-assigned this Aug 8, 2024
@jzheaux
Copy link
Contributor Author

jzheaux commented Aug 8, 2024

@ch4mpy would you please look over this PR as see if is addresses your concerns about CSRF and Spring Session?

@ch4mpy
Copy link
Contributor

ch4mpy commented Aug 12, 2024

@jzheaux I can confirm that, when building the branch for this PR locally, Back-Channel Logout works in this project of mine. The OP is Keycloak. The RP used to trigger the Back-Channel Logout is Keycloak's user account management. The RP receiving the Back-Channel Logout request is a spring-cloud-gateway instance (the reactive version) configured with:

  • oauth2Login
  • cookie-based protection against CSRF (because it is called by a SPA authorizing requests with a session cookie)
  • RP-Initiated Logout (keeps working, validating the CSRF token)
  • Back-Channel Logout (fails with latest release, but is successful with this PR)

I can confirm too that configuring the Back-Channel logout URI works as documented in your comment above.

But warning: I had to configure a custom URI because the port for the "internal" request was determined using the original request instead of application properties. My client is behind a reverse proxy that uses a custom hostname and a path prefix for the BFF. The port for the proxy is 80 and the port for the Spring client with oauth2Login and Back-Channel Logout is 7080. The port was resolved to 80, which required the request to go through the reverse-proxy. As you made this URI configurable, I could walk around the problem, but shouldn't OidcBackChannelLogoutHandler resolve "all or none" of scheme, authority, and basePath from the original request?

  • The "all" strategy is appealing because it is easy: we need to call the same endpoint. However, it requires the entry-point of the system to be accessible from the Spring OAuth2 client with the same hostname (if the Spring app runs in a Docker container, it is possible that this hostname is not resolved or resolved to the wrong host).
  • the "none" strategy (URI built around localhost from application properties instead of the original request) has the advantage of working even on dev machines with Spring app running in Docker. But maybe is it acceptable to configure the URI in that case?

@sjohnr sjohnr modified the milestones: 6.4.0-M2, 6.4.0-M3 Aug 19, 2024
@marcusdacoregio marcusdacoregio modified the milestones: 6.4.0-M3, 6.4.0-M4 Aug 22, 2024
@jzheaux jzheaux force-pushed the gh-13841 branch 5 times, most recently from e59dba7 to faf4ce7 Compare September 4, 2024 00:20
This component already uses by default a URI that doesn't require
a CSRF token and aalready allows for configuring a cookie name.

So, by making it public and configurable in the DSL, both
of these tickets quite naturally close.

Closes spring-projectsgh-13841
Closes spring-projectsgh-14904
@jzheaux jzheaux merged commit b311b81 into spring-projects:main Sep 16, 2024
6 checks passed
@jzheaux jzheaux deleted the gh-13841 branch September 16, 2024 04:31
@ch4mpy
Copy link
Contributor

ch4mpy commented Sep 27, 2024

@jzheaux I like what you did with the OidcBackChannelServerLogoutHandler bean to configure Back-Channel Logout. It made my life very easy to adapt my starter to enable this feature and configure it with application properties. Thank you.

@dreamstar-enterprises
Copy link

dreamstar-enterprises commented Aug 8, 2025

I maybe barking up the wrong tree here, but this first principles approach worked for me, in a way that I could also understand. I'm sure people could adapt the below to their needs too

**
* Configuration properties for server endpoints and authentication settings.
*
* @property route53Host AWS Route53 host for production routing
* @property route53Port AWS Route53 port number
* @property reverseProxyHost Reverse proxy host for development routing
* @property reverseProxyPort Reverse proxy port number
* @property bffServerPrefix BFF (Backend for Frontend) server prefix
* @property resourceServerHost Resource server host address
* @property resourceServerPort Resource server port number
* @property resourceServerPrefix Resource server endpoint prefix
* @property auth0AuthRegistrationId Auth0 registration identifier
* @property auth0IssuerUri Auth0 issuer URI
* @property auth0ApiAudience Auth0 API audience identifier
* @property auth0DreamstarVivianaUrl Auth0 Dreamstar Viviana URL
* @property inHouseAuthServerPrefix In-house authorization server prefix
* @property inHouseAuthRegistrationId In-house authorization registration ID
* @property sslProperties SSL configuration properties
*/
@ConfigurationProperties(prefix = "dse-viviana-servers")
internal class ServerProperties(
   private val springProfileProperties: SpringProfileProperties,
   private val sslProperties: SslProperties
) {

   /*************************/
   /* SERVERS               */
   /*************************/

   /**
    * Route53 Configuration
    */
   var route53Host: String? = null
   var route53Port: Int? = null

   /**
    * Constructs the complete Route53 URI using HTTPS scheme (External Calls)
    */
   val route53Uri: String
       get() = "${HttpSchemeTypesEnum.HTTPS}://$route53Host:$route53Port"

   /**
    * Reverse Proxy Configuration
    */
   var reverseProxyHost: String? = null
   var reverseProxyPort: Int? = null

   /**
    * Constructs the complete Reverse Proxy URI using HTTP scheme (External Calls)
    */
   val reverseProxyUri: String
       get() = "${HttpSchemeTypesEnum.HTTP}://$reverseProxyHost:$reverseProxyPort"

   /**
    * BFF Server Configuration
    */
   var bffServerHost: String? = null
   var bffServerPort: Int? = null
   var bffServerPrefix: String? = null

   /**
    * Constructs the complete BFF URI using HTTP scheme (Internal Calls)
    */
   val bffUri: String
       get() {
           val scheme = if (sslProperties.sslEnabled) HttpSchemeTypesEnum.HTTPS else HttpSchemeTypesEnum.HTTP
           return "$scheme://$bffServerHost:$bffServerPort"
       }

   /**
    * Determines the client URI based on active profile (External Calls)
    * @return URI string for client communications
    */
   val clientUri: String
       get() = when (springProfileProperties.activeProfile) {
           ProfileTypesEnum.DEVELOPMENT.type -> "$reverseProxyUri$bffServerPrefix"
           ProfileTypesEnum.PRODUCTION.type -> "$route53Uri$bffServerPrefix"
           else -> "$reverseProxyUri$bffServerPrefix"
       }

   /**
    * Resource Server Configuration
    */
   var resourceServerHost: String? = null
   var resourceServerPort: Int? = null
   var resourceServerPrefix: String? = null

   /**
    * Constructs the complete Resource Server URI using HTTP scheme (Internal Routing)
    */
   val resourceServerUri: String
       get() = "${HttpSchemeTypesEnum.HTTP}://$resourceServerHost:$resourceServerPort"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants