You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Verify email not working in incognito browser tab after Keycloak restart #51088
I have read and understood the above terms for submitting issues, and I understand that my issue may be closed without action if I do not follow them.
Area
authentication
Describe the bug
When a user registers he receives the email containing the link to verify the email address.
When the Keycloak server is restarted after the email was sent and the user clicks the link afterwards in a browser incognito window, the server responds with a 500.
This behaviour is reproducible and I can already provide you with a fix.
Version
26.7.0
Regression
The issue is a regression
Expected behavior
Email verification link should always be working, even after Keycloak restart and opening it in an incognito browser window.
Actual behavior
Opening the link in an incognito browser window after Keycloak restart responds with a 500.
How to Reproduce?
Fill in the user registration form and submit.
Make sure, verification email was received.
Restart Keycloak
Open link in verification email in an incognito window of any browser.
This looks like a classic Infinispan cache eviction issue. Let me walk through the likely root cause and the fix.
Root Cause: In-Memory Token Loss on Restart
Keycloak stores email verification tokens in the actionTokens Infinispan cache by default (in-memory, not persisted). When the server restarts:
The user receives a verification email with a token
Server restarts → Infinispan cache is cleared
User clicks the verification link
Keycloak looks up the token in the (now empty) cache → null
The null token isn't handled gracefully → 500 Internal Server Error
The relevant code path in Keycloak is approximately:
// org.keycloak.services.resources.Account2FactorResource (simplified)ActionTokentoken = tokenStore.getActionToken(tokenHash);
if (token == null) {
// Missing null check — this is where the NPE occurs// Should return 410 Gone or a user-friendly errorthrownewNullPointerException(); // → 500
}
Recommended Fixes
1. Configure Persistent Token Store (Immediate Fix)
Switch from in-memory to a file-based or JDBC token store:
Even with persistent storage, tokens can legitimately expire or be absent. The endpoint should handle this gracefully:
ActionTokentoken = tokenStore.getActionToken(tokenHash);
if (token == null || token.isExpired()) {
// 410 Gone: resource existed but is no longer availablereturnResponse.status(Response.Status.GONE)
.entity(newErrorRepresentation("Verification token is no longer valid."))
.build();
}
Using 410 (not 404) is semantically correct: the token existed but is no longer available. This tells the client the request was valid but the resource is gone, versus 404 which implies it never existed.
3. Security Considerations for Token Persistence
A few notes on the security implications:
Token binding: If tokens are persisted, ensure they're encrypted at rest (especially if they contain PII or sensitive action metadata). Keycloak's default ActionToken serialization includes the user ID and action type.
Token replay window: Persisted tokens may survive longer. Consider adding an explicit TTL check on retrieval:
JDBC store locking: If using JDBC-backed Infinispan, ensure your connection pool is configured with appropriate timeout to avoid thread starvation under load (this is a separate concern from the NPE but commonly co-occurs).
Activity
janderik commentedon Jul 22, 2026
This looks like a classic Infinispan cache eviction issue. Let me walk through the likely root cause and the fix.
Root Cause: In-Memory Token Loss on Restart
Keycloak stores email verification tokens in the
actionTokensInfinispan cache by default (in-memory, not persisted). When the server restarts:nullThe relevant code path in Keycloak is approximately:
Recommended Fixes
1. Configure Persistent Token Store (Immediate Fix)
Switch from in-memory to a file-based or JDBC token store:
<!-- keycloak.conf --> cache-config-file=cache-ispn-persistent.xmlOr for production with a database:
2. Null-Check with 410 Gone (Defensive Fix)
Even with persistent storage, tokens can legitimately expire or be absent. The endpoint should handle this gracefully:
Using 410 (not 404) is semantically correct: the token existed but is no longer available. This tells the client the request was valid but the resource is gone, versus 404 which implies it never existed.
3. Security Considerations for Token Persistence
A few notes on the security implications:
Token binding: If tokens are persisted, ensure they're encrypted at rest (especially if they contain PII or sensitive action metadata). Keycloak's default
ActionTokenserialization includes the user ID and action type.Token replay window: Persisted tokens may survive longer. Consider adding an explicit TTL check on retrieval:
References:
Would be happy to draft a PR for the null-check fix — it's a small, low-risk change that would prevent the 500 regardless of the storage backend.
mm-matthias commentedon Jul 23, 2026
Another workaround for this is using
--feature=stateless. This will persist the root auth session and the following NPE is prevented.rmartinc commentedon Jul 27, 2026
Thanks @pchorus for the PR! This is a regression caused by #45568.
4 remaining items
fix: allows verify email working in incognito browser tab after Keycl…
fix: allows verify email working in incognito browser tab after Keycl…