0

[Series Thực Chiến E-commerce] Bài 6: "Tuyệt chiêu" giữ chân User - Tái cấp phát Access Token bằng Refresh Token

Chào anh em! Lại là mình đây.

Ở Bài 5, chúng ta đã setup thành công cặp đôi hoàn cảnh: Access Token (sống 3 ngày) và Refresh Token (sống 7 ngày, giấu kỹ trong Cookie). Nhưng câu hỏi đặt ra là: Lúc Access Token hết hạn, hệ thống trả về lỗi 401 Unauthorized, chẳng lẽ lại hiện popup bắt user văng ra ngoài đăng nhập lại?

Làm thế thì bộ phận Chăm sóc khách hàng của dự án sẽ "tế" anh em lên mất. Trải nghiệm người dùng (UX) thời nay là phải mượt. Khi thẻ ngắn hạn hết date, Frontend sẽ âm thầm cầm cái thẻ dài hạn (Refresh Token) chạy xuống trạm gác Backend để xin đổi một cái Access Token mới toanh. User vẫn lướt app mua hàng ầm ầm mà không hề hay biết chuyện gì vừa xảy ra dưới ngầm.

Hôm nay, mình sẽ hướng dẫn anh em viết API "đổi thẻ" này nhé!

1. Viết logic Tái cấp phát (Controller)

Anh em mở file controllers/user.js lên và bổ sung thêm hàm refreshAccessToken vào:

const jwt = require('jsonwebtoken'); // Nhớ import jwt nếu chưa có nhé

const refreshAccessToken = asyncHandler(async (req, res) => {
  // 1. Lấy toàn bộ cookie mà trình duyệt tự động đính kèm gửi lên
  const cookie = req.cookies;

  // 2. Check xem trong túi có Refresh Token không
  if (!cookie || !cookie.refreshToken) {
    throw new Error('Không tìm thấy refresh token trong cookies!');
  }

  // 3. Đưa token vào máy soi (verify) xem có phải hàng giả hay hàng hết hạn không
  jwt.verify(cookie.refreshToken, process.env.JWT_SECRET, async (err, decode) => {
    // Nếu lỗi (hết hạn, sai chữ ký...) thì chặn luôn
    if (err) {
      return res.status(401).json({
          success: false, 
          message: 'Refresh token không hợp lệ hoặc đã hết hạn' 
      });
    }

    // 4. Tuyệt chiêu bảo mật: Token thật, nhưng có phải của User đó trên DB không?
    // Phải query DB để so sánh cái token dưới Cookie với cái token lưu ở Bài 5
    // (💡 Tip nhỏ: Chỗ này anh em cẩn thận gõ nhầm 'User' thành 'Use' là toang server nha)
    const response = await User.findOne({
      _id: decode._id,
      refreshToken: cookie.refreshToken
    });

    // 5. Nếu mọi thứ khớp hoàn hảo, in thẻ Access Token mới đưa cho Frontend
    return res.status(200).json({
      success: response ? true : false,
      newAccessToken: response ? generateAccessToken(response._id, response.role) : "Refresh token không khớp với Database"
    });
  });
});

module.exports = { login, getCurrent, refreshAccessToken }; // Đừng quên export

Kinh nghiệm "xương máu" ở đoạn này: Tại sao bước 4 mình lại phải bắt server tốn công query xuống Database (hàm findOne) làm gì cho mệt, trong khi bước 3 jwt.verify đã báo token chuẩn rồi? Trả lời: Giả sử user này có hành vi gian lận, Admin bực mình vào trang quản trị bấm nút "Khóa tài khoản" và xóa refreshToken của user đó trong DB. Lúc này, dù dưới trình duyệt của user vẫn còn giữ Cookie hợp lệ, nhưng khi gửi lên, nó sẽ không khớp với DB nữa. Hacker/User gian lận chính thức bị cắt đứt nguồn sống! Đây chính là điểm ăn tiền của việc lưu Refresh Token vào Database đó anh em.

2. Mở đường cho API hoạt động (Router)

Logic đã có, giờ chỉ việc kéo dây điện ra ngoài thôi. Anh em mở file routers/user.js và thêm endpoint này:

const express = require('express');
const router = express.Router();
const ctrls = require('../controllers/user');
const { verifyAccessToken } = require('../middlewares/verifyToken');

router.post('/login', ctrls.login);
router.get('/current', verifyAccessToken, ctrls.getCurrent);

// Endpoint mới: Dùng phương thức POST để xin cấp lại token
router.post('/refreshtoken', ctrls.refreshAccessToken);

module.exports = router;

3. Test thử xem "hàng" chạy chưa?

  1. Anh em mở Postman, gọi lại API /login để nhận accessToken mới và lấy Cookie.
  2. Mở một tab mới trong Postman, tạo request POST đến http://localhost:5000/api/user/refreshtoken.
  3. Bấm Send. Anh em sẽ thấy Postman tự động gửi kèm cái Cookie (chứa refreshToken) lên, và Server trả về cho anh em một cục newAccessToken nóng hổi.

Từ giờ, Frontend (React/Vue) chỉ cần viết một con interceptor (như Axios Interceptor). Cứ hễ gọi API nào mà bị server chửi 401, nó sẽ tự động ném request sang thằng /refreshtoken này để lấy thẻ mới, rồi quay lại gọi API cũ. Mượt mà như Sunsilk!

Lời kết

Vậy là luồng Đăng nhập - Cấp token - Gia hạn token của chúng ta coi như đã hoàn thiện và đạt chuẩn bảo mật để có thể đem đi khoe hoặc làm đồ án rồi.

Nhưng cuộc vui nào cũng đến lúc tàn. Người dùng lướt chán rồi muốn Đăng xuất (LogOut) thì làm thế nào? Làm sao để phế võ công cái refreshToken đang nằm trong Cookie và trong Database của họ?

Anh em nghỉ tay uống ngụm nước, mình chuẩn bị chuyển sang Lession 7: LogOut nhé


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí