This is after email delivery
- Email received
- The application, mail transport, provider and receiving mailbox completed enough work to expose the verification URL. Changing SPF, DKIM, SMTP credentials or inbox rules cannot repair a server-side 500 on link use.
- Fresh browser
- With no matching browser authentication session, Keycloak creates a fresh session, embeds the original compound session ID into the confirmation action token and asks the user to confirm.
- Restart
- The default Infinispan authentication-session provider can no longer return the original root session after process state is lost. The handler must tolerate that cleanup target already being absent.
Browser-local routing tool
Locate the Keycloak restart boundary
No verification URL, action token, user ID, email address, realm, session ID, cookie or server log is requested or transmitted.
- First boundary
- Next action
- Exit condition
- Safety rule
What Keycloak 26.7.0 source shows
VerifyEmailActionToken extends DefaultActionToken and carries the compound authentication-session ID, email and client context in the signed action token. The reported path is therefore not proved to be an actionTokens cache lookup failure.
When the request needs a fresh authentication session, VerifyEmailActionTokenHandler copies the original compound session ID into oasid, writes the fresh session ID into the token and renders a confirmation URL.
After confirmation, the handler verifies the user and then reads the original root session. In 26.7.0 it passes that result directly to removeRootAuthenticationSession. The default Infinispan provider can return null for a missing session, while its remove method dereferences the model immediately. That is the concrete null boundary to test.
A minimal defensive correction is to remove the original root session only when the lookup returns a model. Keep signature, expiry, realm, client, email and required-action checks unchanged. A missing cleanup target is not permission to accept an invalid token.
Why stateless can change the result
Keycloak 26.7.0 registers JpaAuthenticationSessionProviderFactory only when the STATELESS feature is enabled. That moves authentication sessions to the database, so the original root session can survive a process restart.
The 26.7.0 release notes describe stateless as part of the preview multi-cluster v2 architecture. Treat it as a topology decision with its own upgrade and load testing, not as a substitute for the null-safe handler fix in every deployment.
Run one bounded four-case control
- Create a controlled user and issue a verification email. Store no action URL in shared logs.
- Test the original browser without a restart and a fresh browser without a restart.
- Repeat each browser case after one deliberate Keycloak restart.
- For every case, record token validation, confirmation rendering, user verification, required-action removal and original-session cleanup separately.
- If the original root lookup is absent, prove the null guard skips only cleanup and still returns the normal verified result.
Regression contract
- Issue one valid verify-email action token tied to an authentication session.
- Simulate the original root session disappearing before a fresh-browser confirmation.
- Assert the user is verified exactly once, the required action is removed and the response is not 500.
- Assert an existing original session is still removed in the normal cross-browser case.
- Assert expired, malformed, wrong-client and replayed tokens keep their existing rejection behavior.
- Assert a post-verification rendering or cleanup failure never causes a duplicate verification message to be issued automatically.
Build a local incident plan across action-token validation, application state, provider delivery and receiver evidence without entering a link or account identifier.
Primary references
- Keycloak issue 51088: verify email fails after restart in incognito
- Keycloak 26.7.0 verify-email action token
- Keycloak 26.7.0 verify-email handler
- Keycloak 26.7.0 Infinispan authentication-session provider
- Keycloak 26.7.0 JPA authentication-session provider factory
- Keycloak 26.7.0 release notes: stateless multi-cluster preview