如何在NodeJS中为RESTful API实现令牌认证及前端调用最优方案
Hey there! Let's walk through implementing token-based authentication for your Node.js REST API, plus the most solid approach for frontend apps to call these secured endpoints. I’ve built this setup countless times, so let’s break it down step by step.
JWT (JSON Web Tokens) is the standard for token auth in REST APIs—lightweight, self-contained, and easy to implement. Here’s how to set it up:
1. Install Dependencies
First, grab the packages we’ll need:
npm install express jsonwebtoken bcryptjs dotenv
express: Our core API frameworkjsonwebtoken: To generate and verify JWT tokensbcryptjs: To hash user passwords (never store plain text credentials!)dotenv: To keep sensitive values like JWT secrets out of code
2. User Registration (Password Hashing)
When a user signs up, we hash their password before saving it to the database. Here’s a simplified example route:
const express = require('express'); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); require('dotenv').config(); const router = express.Router(); // Mock database (replace with your actual DB like MongoDB/PostgreSQL) let users = []; // Register endpoint router.post('/register', async (req, res) => { try { const { email, password } = req.body; // Check if user already exists const existingUser = users.find(u => u.email === email); if (existingUser) return res.status(400).json({ message: 'User already exists' }); // Hash password (10 salt rounds strike a good balance of security/speed) const hashedPassword = await bcrypt.hash(password, 10); // Save user to "database" const newUser = { id: Date.now(), email, password: hashedPassword }; users.push(newUser); res.status(201).json({ message: 'User registered successfully' }); } catch (err) { res.status(500).json({ message: 'Server error' }); } });
3. Login (Generate Access & Refresh Tokens)
When a user logs in, verify their password and issue tokens. We use short-lived access tokens and longer refresh tokens for seamless sessions:
// Login endpoint router.post('/login', async (req, res) => { try { const { email, password } = req.body; // Find user by email const user = users.find(u => u.email === email); if (!user) return res.status(400).json({ message: 'Invalid credentials' }); // Verify password match const isMatch = await bcrypt.compare(password, user.password); if (!isMatch) return res.status(400).json({ message: 'Invalid credentials' }); // Generate short-lived access token (1 hour) const accessToken = jwt.sign( { userId: user.id, email: user.email }, process.env.JWT_SECRET, { expiresIn: '1h' } ); // Generate refresh token (7 days) const refreshToken = jwt.sign( { userId: user.id }, process.env.JWT_REFRESH_SECRET, { expiresIn: '7d' } ); // Attach refresh token to user (in production, save to your database) user.refreshToken = refreshToken; res.json({ accessToken, refreshToken }); } catch (err) { res.status(500).json({ message: 'Server error' }); } });
4. Auth Middleware (Protect Routes)
Create a middleware to verify JWT tokens on protected endpoints:
// Authentication middleware const authenticateToken = (req, res, next) => { // Extract token from Authorization header (format: Bearer <token>) const authHeader = req.headers['authorization']; const token = authHeader && authHeader.split(' ')[1]; if (!token) return res.status(401).json({ message: 'No token provided' }); // Verify token validity jwt.verify(token, process.env.JWT_SECRET, (err, user) => { if (err) return res.status(403).json({ message: 'Invalid or expired token' }); req.user = user; // Attach user data to request for use in routes next(); }); }; // Example protected route router.get('/profile', authenticateToken, (req, res) => { res.json({ message: 'Profile data', user: req.user }); });
Now, how should your frontend handle tokens to call the API securely? Here’s the best approach:
1. Token Storage: Prioritize HttpOnly Cookies (When Possible)
- HttpOnly + Secure Cookies: For same-domain apps (frontend and backend on the same domain/subdomain), store refresh tokens in HttpOnly, Secure cookies. This prevents XSS attacks because JavaScript can’t access HttpOnly cookies.
- Set cookies from the backend like this:
res.cookie('refreshToken', refreshToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', // Only send over HTTPS in production maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days sameSite: 'strict' // Mitigate CSRF risks });
- Set cookies from the backend like this:
- LocalStorage: Use this only for cross-domain APIs where cookies aren’t feasible. Note: LocalStorage is vulnerable to XSS, so ensure your frontend sanitizes all user input thoroughly.
2. Automate Token Injection with Request Interceptors
Use a library like Axios to automatically attach the access token to every protected request:
import axios from 'axios'; const api = axios.create({ baseURL: 'http://your-api-domain.com/api' }); // Add interceptor to attach access token to requests api.interceptors.request.use((config) => { const accessToken = localStorage.getItem('accessToken'); if (accessToken) { config.headers.Authorization = `Bearer ${accessToken}`; } return config; }); export default api;
3. Implement Token Refresh Logic
Avoid forcing users to log in every hour by adding a refresh flow:
- If the frontend gets a
403 Forbidden(access token expired), call a/refresh-tokenendpoint with the refresh token. - The backend verifies the refresh token, generates a new access token, and sends it back.
- The frontend saves the new access token and retries the original request.
Example frontend refresh logic:
api.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config; // Retry only once if token expired if (error.response.status === 403 && !originalRequest._retry) { originalRequest._retry = true; try { const refreshToken = localStorage.getItem('refreshToken'); const response = await api.post('/refresh-token', { refreshToken }); const newAccessToken = response.data.accessToken; localStorage.setItem('accessToken', newAccessToken); // Update auth header for the original request originalRequest.headers.Authorization = `Bearer ${newAccessToken}`; return api(originalRequest); } catch (err) { // If refresh fails, redirect to login window.location.href = '/login'; return Promise.reject(err); } } return Promise.reject(error); } );
4. Error Handling & Logout
- If a
401 Unauthorizedis returned (invalid token or no token), immediately redirect the user to the login page. - On logout, clear the access token from storage and invalidate the refresh token on the backend (e.g., remove it from your database).
- Short-Lived Access Tokens: Keep access tokens to 15-60 minutes to minimize damage if leaked.
- HTTPS Everywhere: Always use HTTPS to encrypt token transmission and prevent man-in-the-middle attacks.
- Token Revocation: For critical apps, implement a refresh token blacklist to revoke tokens if a user logs out or their account is compromised.
- Avoid Sensitive Data in JWT: JWTs are easily decoded—never store passwords or sensitive information in them.
内容的提问来源于stack exchange,提问作者Pasquale De Lucia




