If you're using Keycloak in a React, Vue, or vanilla JS app and your users are being forced to log in again after every page refresh, you're not alone.
This post explains why it happens, and shows you how to persist authentication with clear examples and best practices.
🔍 The Problem
By default, keycloak-js
stores your tokens (accessToken, refreshToken, idToken) in memory only.
So when your Single Page App (SPA) reloads:
- The tokens are gone
- The user appears logged out
- Keycloak redirects to login again
✅ Solution 1: Store Tokens in Local/Session Storage
Step 1: Save Tokens After Login
keycloak.init({ onLoad: 'login-required' }).then((authenticated) => {
if (authenticated) {
localStorage.setItem('kc_token', keycloak.token);
localStorage.setItem('kc_refreshToken', keycloak.refreshToken);
localStorage.setItem('kc_idToken', keycloak.idToken);
}
});
Step 2: Restore Tokens on Page Load
const keycloak = new Keycloak({
url: 'https://your-keycloak-domain/auth',
realm: 'your-realm',
clientId: 'your-client-id',
});
const token = localStorage.getItem('kc_token');
const refreshToken = localStorage.getItem('kc_refreshToken');
keycloak.init({
onLoad: 'login-required',
token,
refreshToken,
}).then((authenticated) => {
if (!authenticated) keycloak.login();
});
Step 3: Auto-Refresh the Token
setInterval(() => {
keycloak.updateToken(70).then((refreshed) => {
if (refreshed) {
localStorage.setItem('kc_token', keycloak.token);
localStorage.setItem('kc_refreshToken', keycloak.refreshToken);
}
}).catch(() => {
console.error('Token refresh failed');
keycloak.logout();
});
}, 60000);
⚠️ Security Considerations
Storage | Pros | Cons | Security Level |
---|---|---|---|
localStorage |
Easy to use, persists reloads | XSS vulnerable | 🔓 Low-Medium |
sessionStorage |
Safer (cleared on tab close) | Less persistent | 🔒 Medium |
HttpOnly Cookies |
Most secure, no JS access | Requires backend setup | 🔐 High |
✅ Best Practices
- Use CSP headers
- Sanitize all inputs
- Avoid
innerHTML
- Prefer
sessionStorage
unless persistence is a must
🛠 Solution 2: Use Silent SSO (check-sso
)
If you don’t want to manually store tokens, use silent SSO.
Create silent-check-sso.html
<!DOCTYPE html>
<html>
<body>
<script>
parent.postMessage(location.href, location.origin);
</script>
</body>
</html>
Update Your Init
keycloak.init({
onLoad: 'check-sso',
silentCheckSsoRedirectUri: window.location.origin + '/silent-check-sso.html',
}).then((authenticated) => {
if (!authenticated) keycloak.login();
});
Heads up: Some browsers block third-party cookies, which can break Silent SSO in cross-domain setups.
🔐 Solution 3: Use HttpOnly Cookies (Backend Required)
For production-level security, handle tokens server-side with HttpOnly cookies.
Why?
- Tokens can’t be accessed via JS (safe from XSS)
- Automatically attached to requests
- Requires your backend (Node, Django, etc.) to manage tokens
This is ideal for highly secure applications or enterprise needs.
✅ Summary
Method | Use when... |
---|---|
localStorage |
Simplicity is more important than security |
sessionStorage |
You want better security, no persistence |
Silent SSO | You prefer not storing tokens at all |
HttpOnly Cookies | You’re building a secure, production-grade app |
Top comments (2)
This is completly wrong and dangerous. Never store token in local, session or cookie. it is not secure. Keycloak-js don't do it for a good reason. If the user refresh the page, the token is lost, that is true. But this is not a problem as soon as your application redirect the user to keycloak. Keycloak will then read its own cookie, emit a new token and redirect back the user to the application almost instantly.
Solution 3, just break the entire openid connect thing by forcing the user to send its credential to the bakend and losing sso feature.
So if you have the problem describe in this article, just make your application do the redirect.
Thank you for your comment — you’ve raised valid concerns about token security, and we completely agree that blindly storing tokens in localStorage or sessionStorage without proper safeguards can be dangerous. That’s exactly why Keycloak-js avoids doing so by default.
However, in complex SPA environments — particularly where routing and seamless user experience are critical — relying solely on Keycloak’s default redirect-based login flow may not always be practical. Controlled token persistence, if implemented correctly, can prevent unnecessary logins on refresh without breaking the OpenID Connect flow.
It’s also important to consider that many modern browsers are now blocking Keycloak’s silent refresh mechanism (via hidden iframes), especially when Keycloak is hosted on a different domain — a common setup in microservice architectures. In such setups, iframe-based token renewal fails due to third-party cookie restrictions, leaving users stuck or logged out.
To address this, we use a strongly encrypted storage approach that ensures tokens are not stored as plain text and are inaccessible to unauthorized scripts — mitigating the risk of token theft via XSS. Combined with strict CSP headers and robust input sanitization, this approach balances usability with security.
That said, we fully support redirect-based login as the most secure out-of-the-box option. Our solution is an alternative for edge cases — meant for experienced teams who understand the trade-offs and implement rigorous security practices.
Appreciate your input — it’s a vital part of evolving secure SPA authentication strategies!