基于Node.js+Express,如何替换express-session改用客户端存储JWT令牌?
Got it, let's walk through how to transition from express-session's cookie-based auth to JWT tokens while keeping your existing Passport local setup and MongoDB stack. I've broken this down into actionable steps tailored to your tech stack:
First, add the packages needed to handle JWT creation, validation, and Passport integration:
npm install jsonwebtoken passport-jwt
You should already have passport-local, mongoose, and bcryptjs (for password hashing) from your existing setup, but double-check they're installed if not.
Ensure your user schema has password hashing logic (if it doesn't already) and a method to compare passwords. Here's a standard example:
const mongoose = require('mongoose'); const bcrypt = require('bcryptjs'); const userSchema = new mongoose.Schema({ email: { type: String, required: true, unique: true }, password: { type: String, required: true } }); // Hash password before saving to DB userSchema.pre('save', async function(next) { if (!this.isModified('password')) return next(); this.password = await bcrypt.hash(this.password, 12); next(); }); // Method to compare candidate password with stored hash userSchema.methods.comparePassword = async function(candidatePassword) { return await bcrypt.compare(candidatePassword, this.password); }; module.exports = mongoose.model('User', userSchema);
Create a Passport config file (e.g., config/passport.js) to set up JWT validation. This tells Passport how to extract tokens from requests and verify them against your MongoDB users:
const passport = require('passport'); const JwtStrategy = require('passport-jwt').Strategy; const ExtractJwt = require('passport-jwt').ExtractJwt; const User = require('../models/User'); const opts = { // Extract token from the "Authorization: Bearer <token>" header jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), // Use an environment variable for the secret (never hardcode this!) secretOrKey: process.env.JWT_SECRET }; passport.use(new JwtStrategy(opts, async (jwtPayload, done) => { try { const user = await User.findById(jwtPayload.id); if (user) { return done(null, user); // Attach user to req.user for protected routes } return done(null, false); // No user found } catch (err) { return done(err, false); } })); module.exports = passport;
Replace your existing session-based login route with one that issues a JWT after successful authentication. You'll also add a protected route example to test the JWT validation:
const express = require('express'); const router = express.Router(); const jwt = require('jsonwebtoken'); const User = require('../models/User'); // Login route: Verify credentials and return JWT router.post('/login', async (req, res) => { try { const { email, password } = req.body; const user = await User.findOne({ email }); if (!user) { return res.status(401).json({ message: 'Invalid credentials' }); } const isPasswordMatch = await user.comparePassword(password); if (!isPasswordMatch) { return res.status(401).json({ message: 'Invalid credentials' }); } // Generate JWT (expires in 1 hour - adjust as needed) const token = jwt.sign( { id: user._id, email: user.email }, process.env.JWT_SECRET, { expiresIn: '1h' } ); res.json({ token }); // Send token to client } catch (err) { res.status(500).json({ message: 'Server error' }); } }); // Protected route: Requires valid JWT to access router.get('/profile', require('../config/passport').authenticate('jwt', { session: false }), (req, res) => { // req.user contains the authenticated user object res.json({ id: req.user._id, email: req.user.email }); }); module.exports = router;
Since you're moving away from session cookies, remove the express-session middleware and passport.session() from your main app file. Keep only passport.initialize():
const express = require('express'); const passport = require('./config/passport'); const authRoutes = require('./routes/auth'); const app = express(); // Middleware app.use(express.json()); app.use(passport.initialize()); // Remove passport.session() entirely // Routes app.use('/api/auth', authRoutes); // Start server const PORT = process.env.PORT || 5000; app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
On the client, store the JWT after login (usually in localStorage or sessionStorage) and include it in the Authorization header for every protected request:
// Login function async function login(email, password) { const response = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) }); const data = await response.json(); if (response.ok) { localStorage.setItem('jwtToken', data.token); } else { alert(data.message); } } // Request protected profile data async function fetchProfile() { const token = localStorage.getItem('jwtToken'); const response = await fetch('/api/auth/profile', { headers: { 'Authorization': `Bearer ${token}` } }); const profile = await response.json(); console.log(profile); }
- Always use HTTPS: JWTs are plaintext in transit, so HTTPS prevents interception.
- Secure your JWT secret: Store it in an environment variable (never commit it to version control).
- Limit token expiration: Short-lived tokens reduce risk if stolen. Consider adding a refresh token flow for longer sessions.
- Mitigate XSS risks: Storing JWTs in
localStorageexposes them to XSS attacks. Ensure your frontend has strong XSS protections, or use HttpOnly cookies for refresh tokens if you implement that flow.
内容的提问来源于stack exchange,提问作者AxDu

