You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在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.

Backend Implementation (Node.js + JWT)

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 framework
  • jsonwebtoken: To generate and verify JWT tokens
  • bcryptjs: 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 });
});
Frontend Optimal Implementation

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
      });
      
  • 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:

  1. If the frontend gets a 403 Forbidden (access token expired), call a /refresh-token endpoint with the refresh token.
  2. The backend verifies the refresh token, generates a new access token, and sends it back.
  3. 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 Unauthorized is 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).
Key Best Practices
  • 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

火山引擎 最新活动