Skip to content
Flag of Europe
Made in the European Union · Independently built · Released under EUPL 1.2
Session Policy

Session Policy

A session that lives forever is a security risk; a session that gets killed mid-request is a UX disaster. SessionPolicy lets the application define both kinds of limits in one place, and it lets the adapters enforce them consistently.

Status: implemented. Feature §4 in Konzept-V00.60.00.md. Delivered 2026-05-10; demos opt in via SPI; session-id rotation on login landed 2026-05-11.

The contract

public interface SessionPolicy<U> {
  SessionDecision evaluate(SessionMetadata metadata);
  default boolean rotateSessionAfterLogin() { return true; }
}

SessionDecision is sealed:

public sealed interface SessionDecision {
  record Continue() implements SessionDecision {}
  record Invalidate(String reason) implements SessionDecision {}
}

SessionMetadata carries:

  • subjectId
  • createdAt, lastActiveAt
  • idleTimeout, absoluteTimeout
  • remoteAddress (when the adapter has it)

The policy is a pure query — it doesn’t mutate, it doesn’t reach into transport-specific state. Adapters call it before honouring a session and act on the result.

Defaults

  • NoopSessionPolicy — always Continue. Useful for tests.
  • TimeoutSessionPolicy — checks both idle and absolute limits, returns Invalidate(reason) when either is exceeded. Reason strings are stable so audit consumers can rely on them ("idle-timeout", "absolute-timeout").

Session-id rotation after login

After a successful login, the policy’s rotateSessionAfterLogin() hook (default true) triggers VaadinService.reinitializeSession(...) — a fresh session id, new servlet session, but the same Vaadin UI state preserved through the re-initialization. This is the session-fixation defence: an attacker who got hold of the pre-login session id can no longer use it after login.

Implementation lives in the Vaadin adapter (commit 0f53cd5, B3 — “honour SessionDecision.Invalidate from onLogin via session-id rotation”).

Vaadin enforcement

The SessionLifetimeListener in security-vaadin consults the policy on every Vaadin event that touches a session. On Invalidate(reason):

  1. The reason is published as SessionExpired or SessionInvalidated on the audit pipeline.
  2. The Vaadin session is closed; the HTTP session is invalidated.
  3. The user is redirected to the login route via the existing logout listener chain — same code path as a manual logout.

REST enforcement

RestAuthorizationFilter consults SessionPolicy.evaluate(...) once the subject is resolved from the request. On Invalidate(reason) the filter returns 401 Unauthorized and (where applicable) revokes the bearer token through the logout listener chain.

A REST client receiving 401 mid-conversation knows to re-authenticate.

Configuration

Like the brute-force policy, TimeoutSessionPolicy reads its limits through a …ConfigurationLoader with the sysprop → env → default chain:

System propertyEnv varDefault
security.session.idle-timeoutSECURITY_SESSION_IDLE_TIMEOUTPT30M
security.session.absolute-timeoutSECURITY_SESSION_ABSOLUTE_TIMEOUTPT8H
security.session.rotate-after-loginSECURITY_SESSION_ROTATE_AFTER_LOGINtrue

Plugging in your own

public final class MyTenantAwareSessionPolicy implements SessionPolicy<MyUser> {
  @Override
  public SessionDecision evaluate(SessionMetadata m) {
    if (tenants.isSuspended(m.subjectId().tenantId())) {
      return new SessionDecision.Invalidate("tenant-suspended");
    }
    return defaultPolicy.evaluate(m);
  }
}

Register in META-INF/services/com.svenruppert.vaadin.security.session.SessionPolicy. Both adapters pick it up via SecurityServiceResolver.