Skip to content

Session stores and clustering

Jérôme LELEU edited this page Sep 29, 2026 · 2 revisions

The org.pac4j.vertx.core.store package provides implementations of pac4j's generic Store<K, V> interface. These can hold SSO logout mappings shared by Vert.x instances. They complement the HTTP session store.

Which store does what?

Component Role
Vert.x LocalSessionStore or ClusteredSessionStore (io.vertx.ext.web.sstore) Stores HTTP sessions, including their attributes and authenticated profiles. Used by Vert.x SessionHandler.
VertxSessionStore (org.pac4j.vertx.context.session) Adapts the application's Vert.x session store to pac4j's SessionStore API.
VertxLocalMapStore<K, V> (org.pac4j.vertx.core.store) Implements pac4j Store using a Vert.x local shared map.
VertxClusteredMapStore<K, V> (org.pac4j.vertx.core.store) Implements pac4j Store using a Vert.x asynchronous shared map, with synchronous waits for the pac4j API.

The generic Store can be supplied to DefaultSessionLogoutHandler. With VertxSessionStore, the handler records two mappings: the identity provider's session key (for example a CAS ticket) to the application's session identifier, and the reverse mapping. The profiles remain in the HTTP session store.

These implementations are optional public components. They are not selected automatically. The current demo uses LocalSessionStore and does not configure either core.store implementation.

Configure SSO logout mappings

For local mappings shared by components of the same Vert.x instance:

import org.pac4j.core.logout.handler.DefaultSessionLogoutHandler;
import org.pac4j.vertx.core.store.VertxLocalMapStore;

config.setSessionLogoutHandler(new DefaultSessionLogoutHandler(
    new VertxLocalMapStore<String, Object>(vertx)
));

For mappings shared across cluster members:

import org.pac4j.core.logout.handler.DefaultSessionLogoutHandler;
import org.pac4j.vertx.core.store.VertxClusteredMapStore;

config.setSessionLogoutHandler(new DefaultSessionLogoutHandler(
    new VertxClusteredMapStore<String, Object>(vertx)
));

Configure this before the clients are initialized or the first authentication request is processed. Each application instance needs this configuration. For cross-node operation, the supplied vertx instances must belong to the same cluster; merely constructing VertxClusteredMapStore does not create a cluster. See the Vert.x Hazelcast cluster manager documentation for startup and dependencies.

VertxClusteredMapStore waits for Vert.x futures and must be used from a worker thread, such as within executeBlocking. Its constructor also accepts a timeout in seconds; the default is one second per awaited operation.

Both map implementations use the shared map name pac4jSharedData. They do not configure entry expiration or a maximum size. Applications should account for abandoned mappings and the lifetime of this map when choosing a store for production.

Configure the HTTP sessions as well

Sharing SSO mappings does not share the actual browser sessions. When logout can arrive on another member, configure a shared HTTP session store too. Given an application-owned clustered vertx, router, and pac4j config:

import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.handler.SessionHandler;
import io.vertx.ext.web.sstore.ClusteredSessionStore;
import org.pac4j.vertx.context.session.VertxSessionStore;
import org.pac4j.vertx.handler.impl.CallbackHandler;
import org.pac4j.vertx.handler.impl.CallbackHandlerOptions;

var webSessions = ClusteredSessionStore.create(vertx);
var sessionStore = new VertxSessionStore(webSessions);

// Required with 7.0.x; also accepted by later versions.
config.setSessionStoreFactory(parameters -> sessionStore);

router.route().handler(SessionHandler.create(webSessions));
router.route("/callback").handler(BodyHandler.create());
router.route("/callback").handler(new CallbackHandler(
    vertx, sessionStore, config,
    new CallbackHandlerOptions().setDefaultUrl("/")
));

Use the same HTTP session map name on all members, and also configure DefaultSessionLogoutHandler with VertxClusteredMapStore as shown above. The application owns the Vert.x instance, cluster configuration, session lifetime, cookie settings, and route ordering.

In the 7.1 development code, the security, callback, and logout handler constructors supply a session-store factory from their sessionStore argument when none is configured. An explicitly configured factory is preserved.

Front-channel and back-channel logout

Front-channel logout arrives through the browser and can carry its session cookie. Back-channel logout is sent directly by the identity provider, without that browser cookie. DefaultSessionLogoutHandler then looks up the mapping and asks VertxSessionStore.buildFromTrackableSession to load the corresponding HTTP session.

The handler removes the profiles and can optionally destroy the whole session:

var logout = new DefaultSessionLogoutHandler(
    new VertxClusteredMapStore<String, Object>(vertx)
);
logout.setDestroySession(true);
config.setSessionLogoutHandler(logout);

This destroySession setting controls SSO session logout. LogoutHandlerOptions.destroySession is a separate option for the application's logout endpoint.

Back-channel persistence

The restored integration test confirmed a persistence issue in the implementation preceding the 7.1 correction: with a shared store returning a detached session object, back-channel logout removed the SSO mappings but failed to persist profile removal or delete the stored session. The browser could remain authenticated. Configuring VertxClusteredMapStore alone does not resolve this second step.

The correction prepared for 7.1.0-SNAPSHOT explicitly saves changes to tracked sessions with the underlying session store's put method and removes destroyed sessions with delete. It waits for completion and propagates storage failures. Sessions attached to the current HTTP request continue to be persisted by Vert.x SessionHandler.

The test verifies the session in the backing store and makes another browser request on the original node, rather than treating a successful callback response as proof of logout. All five integration scenarios now pass locally, including both values of destroySession. This documents the development correction; it does not indicate that a fixed version has been released.

Integration test

CasLogoutIntegrationTest restores the scenarios of the former Kotlin VertxCasLogoutHandlerIntegrationTest using the current Java/JUnit, pac4j, and Vert.x APIs. It exercises the actual CasClient, CallbackHandler, DefaultSessionLogoutHandler, VertxClusteredMapStore, and ClusteredSessionStore.

Two Hazelcast members run on loopback with an isolated cluster name and temporary ports. A local HTTP server simulates CAS ticket validation. Login occurs on one member and logout on the other. No external identity provider is required.

The test runs automatically with the standard suite in the 7.1 development sources:

mvn test

To run it alone:

mvn -Dtest=CasLogoutIntegrationTest test

It covers shared session/profile data, both directions of SSO mappings, and front-channel and back-channel logout with and without destroying the session. The two back-channel cases reproduced the defect before the correction and pass with it. No profile, skip annotation, or expected-failure assertion hides these checks.

Clone this wiki locally