Node.js请求间会话维护及外部系统Token定时缓存实现咨询
Hey there! Let’s tackle your two Node.js challenges step by step—first, maintaining sessions between requests, and second, that tricky external token caching with auto-refresh requirement. I’ve got practical, tested solutions that should fix the issues you ran into with express-session and cookie-session.
If express-session didn’t work as expected before, it’s likely due to missing critical configuration or using the default in-memory store (which is only for development). Here’s a production-ready setup:
1. Basic express-session Configuration
First, install dependencies:
npm install express-session connect-redis redis
Then set up the session with a Redis store (persistent, works across server restarts/scaling):
const express = require('express'); const session = require('express-session'); const RedisStore = require('connect-redis').default; const redis = require('redis'); const app = express(); // Initialize Redis client const redisClient = redis.createClient({ url: 'redis://localhost:6379' // Update with your Redis URL }); redisClient.connect().catch(console.error); // Configure session middleware app.use(session({ secret: 'your-ultra-strong-secret-key-change-this-in-production', // Required for signing cookies resave: false, // Don't resave session if no changes saveUninitialized: false, // Don't save empty sessions (e.g., for unlogged users) store: new RedisStore({ client: redisClient }), // Use Redis for persistent storage cookie: { secure: process.env.NODE_ENV === 'production', // Only send cookie over HTTPS in production httpOnly: true, // Prevent frontend JS access (security best practice) maxAge: 24 * 60 * 60 * 1000 // Session expires after 1 day } })); // Example login route to store user data in session app.post('/login', (req, res) => { // Replace with your actual user authentication logic const user = { id: 123, username: 'karthick' }; req.session.user = user; // Attach user to session res.send('Logged in successfully!'); }); // Example protected route using session data app.get('/profile', (req, res) => { if (!req.session.user) { return res.status(401).send('Please log in first'); } res.send(`Welcome back, ${req.session.user.username}!`); });
This setup ensures sessions persist across server restarts and works in clustered environments—something the default in-memory store can’t do.
Your requirement is to cache a user-specific external token for 1 hour, auto-refresh it when expired, and avoid redundant auth calls. Here’s a clean, maintainable implementation tied to user sessions:
1. Token Utility Function
First, create a utility to fetch the external token:
// tokenUtils.js async function fetchExternalToken(user) { // Replace with your external system's auth API call const response = await fetch('https://external-system.com/auth', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userId: user.id, // Add any other credentials the external system requires }) }); if (!response.ok) { throw new Error('Failed to fetch external token'); } const data = await response.json(); return { token: data.access_token, expiresAt: Date.now() + 3600 * 1000 // Expire after 1 hour (match external token's TTL) }; } module.exports = { fetchExternalToken };
2. Update Login Flow to Store Token
Modify your login route to fetch the external token and save it to the user’s session:
const { fetchExternalToken } = require('./tokenUtils'); app.post('/login', async (req, res) => { try { // Step 1: Authenticate local user (replace with your DB check) const localUser = await db.users.findOne({ username: req.body.username }); if (!localUser || localUser.password !== req.body.password) { return res.status(401).send('Invalid credentials'); } // Step 2: Fetch external token for the user const externalTokenData = await fetchExternalToken(localUser); // Step 3: Save user and token to session req.session.user = localUser; req.session.externalToken = externalTokenData; res.send('Logged in successfully!'); } catch (err) { console.error('Login error:', err); res.status(500).send('Internal server error'); } });
3. Middleware to Auto-Refresh Token
Create a middleware that checks if the token is expired and refreshes it before processing the request:
const { fetchExternalToken } = require('./tokenUtils'); async function ensureValidExternalToken(req, res, next) { // First, ensure user is logged in if (!req.session.user) { return res.status(401).send('Please log in first'); } const currentToken = req.session.externalToken; // Check if token is missing or about to expire (add 1-minute buffer to avoid race conditions) const isTokenExpired = !currentToken || Date.now() > currentToken.expiresAt - 60 * 1000; if (isTokenExpired) { try { // Refresh the token const newTokenData = await fetchExternalToken(req.session.user); req.session.externalToken = newTokenData; } catch (err) { console.error('Failed to refresh external token:', err); return res.status(500).send('Could not connect to external system'); } } // Attach the valid token to the request object for easy access req.externalToken = req.session.externalToken.token; next(); }
4. Use the Middleware in Protected API Routes
Now, use the middleware in any route that needs to call the external system:
app.get('/external-data', ensureValidExternalToken, async (req, res) => { try { // Call the external system's data API with the cached token const response = await fetch('https://external-system.com/api/data', { headers: { Authorization: `Bearer ${req.externalToken}` } }); const data = await response.json(); res.json(data); } catch (err) { console.error('Failed to fetch external data:', err); res.status(500).send('Could not fetch data from external system'); } });
Key Notes
- User-Specific Tokens: Since your token is tied to a logged-in user, storing it in the user’s session makes perfect sense. If the token was global (not user-specific), you could use a Redis key with an expiry instead of session storage.
- Race Condition Prevention: The 1-minute buffer ensures we refresh the token before it actually expires, avoiding cases where the token expires mid-API call.
- Production Readiness: Using Redis for session storage ensures tokens persist across server restarts and scale with your application.
内容的提问来源于stack exchange,提问作者Karthick Shanmugam

