How to Implement KeepSessionAlive in JavaScript and Node.js
Keeping user sessions active is essential for a smooth experience in web apps—especially for apps that perform long-running tasks, require uninterrupted editing, or must maintain authenticated state. This guide shows practical, secure patterns to implement a KeepSessionAlive mechanism using JavaScript on the client and Node.js on the server.
When to use KeepSessionAlive
- Preventing unexpected logouts during extended user activity (editing, filling forms).
- Avoiding repeated re-authentication for single-page apps (SPAs).
- Keeping background jobs tied to a user session alive.
Do not use it to bypass intended security policies (e.g., extremely short session timeouts for sensitive operations).
Overview of approaches
- Heartbeat (regular small pings from client to server)
- Silent refresh (refresh authentication token before expiry)
- WebSocket or Server-Sent Events (persistent connection to indicate activity)
- Session-extend on user activity (mouse/keyboard events reset server timeout)
Server: session basics (Node.js)
- Use a session store (Redis, PostgreSQL, or a database) rather than in-memory store for production.
- Store session expiry and last-activity timestamp.
- Expose an endpoint to receive keepalive pings that updates last-activity or refreshes expiry.
Example stack choices:
- Express with express-session + connect-redis
- JWT with refresh tokens stored server-side (for revocation)
- OAuth2 with refresh tokens (for external auth)
Example implementation: Heartbeat ping (simple, safe)
This approach sends periodic pings from the browser to an endpoint that extends the session expiry.
Server: Express + express-session + connect-redis (minimal)
js
// server.js const express = require(‘express’); const session = require(‘express-session’); const RedisStore = require(‘connect-redis’)(session); const redis = require(‘redis’); const redisClient = redis.createClient({ url: process.env.REDIS_URL }); redisClient.connect().catch(console.error); const app = express(); app.use(express.json()); app.use(session({ store: new RedisStore({ client: redisClient }), secret: process.env.SESSIONSECRET || ‘change-me’, resave: false, saveUninitialized: false, cookie: { maxAge: 30 60 1000 } // 30 minutes })); // Auth-protected route example app.get(’/profile’, (req, res) => { if (!req.session.userId) return res.status(401).send(‘Unauthorized’); res.json({ userId: req.session.userId }); }); // Keepalive endpoint app.post(’/keepalive’, (req, res) => { if (!req.session) return res.sendStatus(401); // Option 1: touch session to extend cookie expiry req.session.touch(); // express-session updates expiry in store res.sendStatus(204); }); app.listen(3000, () => console.log(‘Server running on 3000’));
Notes:
- req.session.touch() updates the session expiry in most stores; ensure your store supports it.
- Use HTTPS to protect cookies.
Client: Browser heartbeat
js
// keepalive.js const KEEPALIVE_INTERVAL_MS = 5 60 1000; // 5 minutes let keepaliveTimer = setInterval(async () => { try { await fetch(’/keepalive’, { method: ‘POST’, credentials: ‘include’ }); } catch (err) { console.warn(‘Keepalive failed’, err); } }, KEEPALIVE_INTERVAL_MS); // Optional: stop when user logs out or page hidden window.addEventListener(‘beforeunload’, () => clearInterval(keepaliveTimer)); document.addEventListener(‘visibilitychange’, () => { if (document.visibilityState === ‘hidden’) { // reduce frequency or pause to save resources } });
Example implementation: Silent refresh for token-based auth (recommended for OAuth/JWT)
Use a short-lived access token + long-lived refresh token. Before the access token expires, request a new access token using the refresh token (server-side or secure HTTP-only cookie).
Flow
- Client stores access token in memory and refresh token in secure, HttpOnly cookie.
- Client sets a timer to call /auth/refresh a minute before expiry.
- Server validates refresh token, issues new access token and rotates refresh token if desired.
Server (sketch)
- Validate refresh token from cookie.
- Issue new access token (JWT) with short expiry.
- Set rotated refresh token cookie with HttpOnly, Secure, SameSite=strict.
Alternative: WebSocket / SSE
- Open a persistent connection and send periodic ping frames or messages.
- Useful if your app already uses sockets; the socket activity can act as implicit keepalive.
- Ensure you still have server-side timeouts and authenticated socket lifecycle.
Security considerations
- Use HTTPS and Secure, HttpOnly cookies.
- Prefer touching server-side session store or rotating tokens rather than extending expiry on client alone.
- Rate-limit keepalive endpoint to avoid abuse.
- Consider idle-timeout policy: only extend sessions when user is actively interacting (mouse, keyboard, touch).
- For highly sensitive apps, prefer short sessions and re-auth prompts over indefinite extension.
UX considerations
- Align keepalive interval to be less than session expiry (e.g., ping every ⁄3 to ⁄2 of expiry).
- Show user warning near expiry with option to continue session.
- Pause keepalive when user is idle for long or on battery-saving modes.
Checklist to implement
- Choose session strategy (server sessions vs tokens).
- Use a robust session store (Redis).
- Implement /keepalive or token refresh endpoint.
- Send periodic client pings or silent refresh before expiry.
- Secure cookies and endpoints (HTTPS, CSRF protection).
- Add rate limiting and activity detection.
Summary
Use heartbeat pings or silent token refresh depending on your auth model. Prefer server-side session touch or refresh-token rotation, secure cookies, and activity-aware keepalive to balance UX and security.
Leave a Reply