Hi,

I have a Spring OAuth Client (BFF), between a Public Angular Client, and an Auth0 Authorization server. When I login, the BFF correctly persists the session to Redis (and with it, the Authorized Client, Security Context, and Authorized Request as attributes in the session)

When I logout though, only the contents of the Session get deleted, the key itself does not. Also nothing in the Sorted Set, ever gets deleted. I am positing here, as it might be a genuine bug.

Logout Handler

Here is my Logout Handler

@Component
internal class SessionServerLogoutHandler(
    private val sessionControl: SessionControl,
    private val sessionProperties: SessionProperties,
    private val csrfProperties: CsrfProperties,
) : ServerLogoutHandler {

    private val logger = LoggerFactory.getLogger(SessionServerLogoutHandler::class.java)

    override fun logout(exchange: WebFilterExchange, authentication: Authentication?): Mono<Void> {
        return exchange.exchange.session
            .flatMap { session ->
                logger.info("Logging out: Invalidating User Session: ${session.id}")

                val response = exchange.exchange.response
                sessionControl.invalidateSession(session)
                    .then(Mono.fromRunnable {

                        logger.info("Deleting Session Cookie: ${sessionProperties.SESSION_COOKIE_NAME}")

                        // delete the session cookie
                        val sessionCookie = ResponseCookie.from(sessionProperties.SESSION_COOKIE_NAME)
                        sessionCookie.maxAge(0)
                        sessionCookie.httpOnly(sessionProperties.SESSION_COOKIE_HTTP_ONLY)
                        sessionCookie.secure(sessionProperties.SESSION_COOKIE_SECURE)
                        sessionCookie.sameSite(sessionProperties.SESSION_COOKIE_SAME_SITE)
                        sessionCookie.path(sessionProperties.SESSION_COOKIE_PATH)
                        sessionCookie.domain(sessionProperties.SESSION_COOKIE_DOMAIN)
                        .build()
                        response.headers.add(
                            HttpHeaders.SET_COOKIE,
                            sessionCookie.toString()
                        )

                        logger.info("Deleting Session Cookie: ${csrfProperties.CSRF_COOKIE_NAME}")

                        // delete the CSRF cookie
                        val csrfCookie = ResponseCookie.from(csrfProperties.CSRF_COOKIE_NAME)
                        csrfCookie.maxAge(0)
                        csrfCookie.httpOnly(csrfProperties.CSRF_COOKIE_HTTP_ONLY)
                        csrfCookie.secure(csrfProperties.CSRF_COOKIE_SECURE)
                        csrfCookie.sameSite(csrfProperties.CSRF_COOKIE_SAME_SITE)
                        csrfCookie.path(csrfProperties.CSRF_COOKIE_PATH)
                        csrfCookie.domain(csrfProperties.CSRF_COOKIE_DOMAIN)
                        .build()
                        response.headers.add(
                            HttpHeaders.SET_COOKIE,
                            csrfCookie.toString()
                        )
                    })
            }
    }
}

SessionControl

It calls another call called SessionControl, and the invalidate session method. Here is that function

fun invalidateSession(session: WebSession): Mono<Void> {
        val sessionInformation = getSessionInformation(session)
        logger.info("Invalidating sessionId: ${sessionInformation.sessionId}")
        // handle the session invalidation process
        return sessionInformation.invalidate()
            .then(Mono.defer {
                webSessionStore.removeSession(sessionInformation.sessionId)
            })
            .doOnSuccess {
                logger.info("Session invalidated and removed: ${sessionInformation.sessionId}")
            }
            .doOnError { error ->
                logger.error("Error invalidating session: ${sessionInformation.sessionId}", error)
            }
    }

WebSessionStore

That inturn calls Websession Store, and it's removeSession method.

Here is the bean and the function:

@Bean(name = ["webSessionStore"])
fun webSessionStore(
    reactiveRedisIndexedSessionRepository: ReactiveRedisIndexedSessionRepository
): SpringSessionWebSessionStore<RedisSession> {
    return SpringSessionWebSessionStore(reactiveRedisIndexedSessionRepository)
}

@Override
public Mono<Void> removeSession(String sessionId) {
    return this.sessions.deleteById(sessionId);
}

reactiveRedisIndexedSessionRepository

The this.sessions above refers to the ReactiveRedisIndexedSessionRepository that was passed in the constructor of the WebsessionStore. Looking at the internals of the Spring ReactiveRedisIndexedSessionRepository I see this:

public Mono<Void> deleteById(String id) {
        return deleteAndReturn(id).then();
    }

    private Mono<RedisSession> deleteAndReturn(String id) {
        // @formatter:off
        return getSession(id, true)
                .flatMap((session) -> this.sessionRedisOperations.delete(getExpiredKey(session.getId()))
                        .thenReturn(session))
                .flatMap((session) -> this.sessionRedisOperations.delete(getSessionKey(session.getId())).thenReturn(session))
                .flatMap((session) -> this.indexer.delete(session.getId()).thenReturn(session))
                .flatMap((session) -> this.expirationStore.remove(session.getId()).thenReturn(session));
        // @formatter:on
    }

Session in Redis before

As you can see before I logout, the session is there in Redis.

Session in Redis after

After I call the logout handler, something has definitely happened, but the session is still there with its key, just no map of values apart from a single lastaccessed map key / value.

Further more nothing ever gets deleted from the SortedSet, which according to the 4th step in the deleteAndReturn method above, it should...

So, can someone help me understand where I may have gone wrong in my code?

Happy to upload the latest full server code to Github too...

Comment From: jzheaux

Thanks for your continued outreach, @dreamstar-enterprises!

There is a lot to parse here and that will make it tricky to provide you timely support. It feels like after you can condense this down to https://stackoverflow.com/help/minimal-reproducible-example[a minimal sample], this question will be better suited to Stack Overflow.

As it is, we prefer to use GitHub issues only for bugs and enhancements. Feel free to update this issue with a link to the re-posted StackOverflow question and we can continue the conversation over there.

Comment From: dreamstar-enterprises

Hi Josh, I did try also testing with reactiveRedisRepository.redisoperations.deleteId(SessionID) - so just one line of code, and got the same result. Here is the issue already documented on Stack Overflow: https://stackoverflow.com/questions/78911034/spring-session-security-redis-sessions-not-being-properly-deleted.

Also, do you know any site, that can provide consultancy support? I've been grappling with SS, for nearly 4 months...and am finding it hard to sort out a final few issues.