0

[Series Thực Chiến E-commerce] Bài 7: "Rút êm" an toàn - Xử lý Đăng xuất (Logout) chuẩn chỉ

Chào anh em! (Link Bài 6)

Chúng ta đã đi qua một chặng đường khá dài với module Authentication (Xác thực người dùng) rồi. Từ lúc cho user đăng ký, đăng nhập, cấp thẻ Access Token ngắn hạn, cho đến việc dùng Refresh Token để xin cấp lại thẻ mới một cách mượt mà.

Mọi thứ đang vận hành trơn tru. Nhưng cuộc vui nào cũng phải đến lúc tàn. Người dùng lướt chán chê, mua sắm xong xuôi và muốn bấm nút "Đăng xuất" (LogOut) thì hệ thống của chúng ta phải làm gì?

Nhiều anh em mới làm thường nghĩ: "Đăng xuất thì cứ để Frontend xóa cái Access Token trong Local Storage là xong chứ gì, gọi API Backend làm chi cho mệt?". Sai lầm chí mạng nhé! Nếu chỉ xóa ở Frontend, cái Refresh Token vẫn còn nằm chình ình trong DB và Cookie. Kẻ gian lấy được nó thì tài khoản của khách vẫn "bay màu" như thường.

Bài 7 hôm nay, chúng ta sẽ viết một API Đăng xuất "sạch sẽ", xóa dấu vết triệt để nhé.

1. Dọn dẹp chiến trường: Logic Đăng xuất (Controller)

Mục tiêu của hàm Logout này rất rõ ràng: Xóa cái Refresh Token đang lưu trong Database, và ra lệnh cho trình duyệt xóa luôn cái Cookie chứa token đó.

Anh em mở file controllers/user.js và thêm đoạn code này vào:

const logout = asyncHandler(async (req, res) => {
  // 1. Lấy cookie từ request gửi lên
  const cookie = req.cookies;

  // 2. Check xem có Refresh Token không. Không có thì chứng tỏ user chưa đăng nhập, hoặc đã out rồi
  if (!cookie || !cookie.refreshToken) {
    throw new Error("Không tìm thấy refresh token trong cookies"); 
    // (Lưu ý: Thực tế anh em có thể return luôn success: true ở đây cũng được, vì mục đích cuối cùng là user không còn đăng nhập)
  }

  // 3. Xóa Refresh Token trong Database
  // Tìm user nào đang giữ cái token này, và update trường refreshToken thành rỗng ('')
  await User.findOneAndUpdate(
    { refreshToken: cookie.refreshToken },
    { refreshToken: '' },
    { new: true }
  );

  // 4. Ra lệnh cho trình duyệt (Browser) xóa cookie chứa token đi
  res.clearCookie('refreshToken', {
    httpOnly: true,
    secure: true // Đảm bảo cookie chỉ được gửi qua HTTPS (khi deploy lên production nhớ chạy HTTPS nhé)
  });

  // 5. Trả về kết quả thành công
  return res.status(200).json({
    success: true,
    message: 'Đăng xuất thành công!'
  });
});

module.exports = { login, getCurrent, refreshAccessToken, logout }; // Nhớ export nhé

💡 Kinh nghiệm thực chiến: Anh em để ý cái hàm res.clearCookie() không? Khi server gọi hàm này, nó sẽ gửi một header xuống cho trình duyệt với thông điệp: "Ê trình duyệt, xóa cái cookie tên là refreshToken đi cho tao". Nhưng để hàm này hoạt động đúng, anh em bắt buộc phải truyền lại đúng các options (httpOnly, secure...) giống hệt như lúc anh em tạo ra cookie bằng res.cookie() ở bài Login. Truyền sai option là trình duyệt nó "bơ" luôn, không chịu xóa đâu!

2. Mở đường API (Router)

Bước này thì quá quen thuộc rồi. Anh em vào file routers/user.js cắm thêm một endpoint cho nó:

const express = require('express');
const router = express.Router();
const ctrls = require('../controllers/user');
// ... các route cũ

// Route Đăng xuất
router.post('/logout', ctrls.logout);

module.exports = router;

Lưu ý: Việc dùng phương thức POST cho hành động LogOut là chuẩn bài (Best Practice). Không nên dùng GET cho LogOut vì các trình duyệt hoặc bot crawl có thể tự động pre-fetch (tải trước) các link GET, vô tình làm user bị đăng xuất ngoài ý muốn.

Tóm lại quy trình LogOut chuẩn sẽ diễn ra như sau:

  1. User bấm nút Đăng xuất trên Giao diện.
  2. Frontend gọi API /api/user/logout. Trình duyệt tự động đính kèm Cookie lên.
  3. Backend xóa token trong DB, ra lệnh xóa Cookie và trả về success: true.
  4. Frontend nhận kết quả, tiến hành xóa nốt cái accessToken đang lưu trong Redux/Local Storage, rồi đẩy user về trang Login.

Sạch sẽ, gọn gàng và không để lại bất kỳ lỗ hổng nào cho hacker khai thác!

Đến đây, chúng ta đã khép lại phần lõi của luồng Xác thực tài khoản (Authentication). Ở Bài 8 tiếp theo, chúng ta sẽ bắt đầu đụng đến một tính năng cực kỳ thực tế và thú vị trong bất kỳ con app E-commerce nào: Gửi Email tự động (Send Mail). Tính năng này sẽ tạo tiền đề để chúng ta làm chức năng "Quên mật khẩu" hoặc "Xác nhận đơn hàng" sau này.


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.