Trong quá trình xây dựng và hoàn thiện các module Authentication Fullstack thực tế, việc xử lý luồng xác thực với JWT (JSON Web Token) luôn là một bài toán thú vị nhưng cũng đầy thử thách. Đây được xem là kỹ năng bắt buộc đối với mọi lập trình viên Front-End hiện đại.
Trong bài blog này, chúng ta sẽ không bàn về những khái niệm cơ bản nữa, mà sẽ "mổ xẻ" sâu vào một nỗi đau (pain point) cực kỳ phổ biến khi triển khai cơ chế Access Token và Refresh Token: Làm sao để xử lý êm đẹp khi có nhiều request đồng thời gửi đi trong lúc Access Token vừa hết hạn?

1. Nỗi đau thực tế: "Cơn bão" Request và Lỗi không đồng bộ
Hãy tưởng tượng kịch bản sau: Người dùng truy cập vào trang Dashboard của ứng dụng. Trang này cần gọi cùng lúc 4 API khác nhau (lấy thông tin user, lấy danh sách sản phẩm, lấy thông báo, lấy thống kê) để hiển thị giao diện.
Đúng lúc đó, Access Token đã hết hạn.
Điều gì sẽ xảy ra nếu chúng ta chỉ viết logic Refresh Token thông thường?
-
Cả 4 API request đồng loạt nhận về mã lỗi
401 Unauthorized. -
Cả 4 request này đều tự động kích hoạt hàm gọi API Refresh Token.
-
Server nhận được 4 yêu cầu cấp lại token cùng lúc. Thông thường, cơ chế bảo mật trên server (như xoay vòng token - token rotation) sẽ chỉ chấp nhận request đầu tiên và lập tức hủy hiệu lực của Refresh Token cũ.
-
Kết quả: 3 request sau bị từ chối, người dùng bị văng ra màn hình đăng nhập (Log out) một cách vô lý dù họ vẫn đang sử dụng app.
Đây chính là hiện tượng Race Condition trong quá trình xác thực.
2. Giải pháp: Cơ chế Khóa (Lock) và Hàng đợi (Queue) với Axios Interceptors
Để khắc phục triệt để vấn đề này, chúng ta cần một "người gác cổng" thông minh. Axios Interceptors chính là công cụ hoàn hảo để làm việc này. Ý tưởng cốt lõi bao gồm hai biến trạng thái:
-
isRefreshing: Một cờ (boolean) để đánh dấu xem ứng dụng có đang trong quá trình gọi API Refresh Token hay không. -
failedQueue: Một mảng (array) đóng vai trò như một phòng chờ, giữ lại tất cả các request bị lỗi401trong lúc chờ token mới.
Quy trình hoạt động diễn ra như sau:
-
Khi request đầu tiên bị lỗi
401, interceptor sẽ kiểm tra cờisRefreshing. Vì là request đầu tiên, cờ này đang làfalse. -
Hệ thống lập tức bật cờ
isRefreshing = truevà gọi API Refresh Token. -
Trong lúc API Refresh Token đang chạy, 3 request còn lại cũng bị lỗi
401và chạy vào interceptor. Lúc này, doisRefreshingđã làtrue, các request này sẽ không gọi lại API cấp token mới nữa. Thay vào đó, chúng được đưa vàofailedQueuedưới dạng các Promise đang chờ (pending). -
Khi API Refresh Token thành công: Hệ thống cập nhật Access Token mới, giải phóng (resolve) toàn bộ các Promise đang nằm trong
failedQueuevới token mới, và cuối cùng gán lạiisRefreshing = false.
3. Quản lý tập trung với Class HttpClient mạnh mẽ
Thay vì viết logic interceptor rải rác khắp nơi, best practice là đóng gói toàn bộ logic này vào một Class HttpClient độc lập. Việc định nghĩa một Class như thế này sẽ mang lại cấu trúc mã nguồn sạch (Clean Code), dễ bảo trì và tái sử dụng.
Bên trong Class HttpClient, bạn có thể thiết lập:
-
Hàm khởi tạo (Constructor) chứa cấu hình Axios base URL và headers mặc định.
-
Các phương thức xử lý Token (như
getAccessToken(),setTokens()). -
Khởi tạo bộ Interceptors để tự động gắn Access Token vào mỗi request gửi đi (Request Interceptor).
-
Xử lý logic Queue và Retry request như đã phân tích ở phần trên (Response Interceptor).
Cách tiếp cận hướng đối tượng này giúp tách biệt hoàn toàn logic gọi API ra khỏi logic giao diện (UI) của React, giúp code dễ dàng mở rộng khi dự án lớn lên.
4. Rủi ro bảo mật: Lưu trữ Token ở localStorage vs. Cookie
Sau khi đã giải quyết xong bài toán luồng logic, một câu hỏi quan trọng không kém được đặt ra: Nên lưu Token ở đâu?
Rất nhiều lập trình viên mới thường có thói quen lưu thẳng cả Access Token và Refresh Token vào localStorage vì nó dễ sử dụng. Tuy nhiên, điều này tiềm ẩn rủi ro rất lớn:
-
localStorage (Dễ bị tấn công XSS): Bất kỳ mã JavaScript nào chạy trên domain của bạn đều có thể đọc được
localStorage. Nếu ứng dụng dính lỗ hổng Cross-Site Scripting (XSS), hacker có thể dễ dàng chèn mã độc để lấy cắp token và chiếm quyền điều khiển tài khoản. -
Cookie với thuộc tính HttpOnly (Ngăn chặn XSS): Đây là phương án bảo mật khuyên dùng cho Refresh Token. Khi set Cookie có cờ
HttpOnly, mã JavaScript phía Front-end (bao gồm cả mã độc) hoàn toàn không thể đọc được giá trị này. Trình duyệt sẽ tự động đính kèm Cookie này trong các request gửi lên server.
Mô hình tối ưu nhất thường được áp dụng:
-
Lưu Refresh Token trong HttpOnly Cookie (Do server set).
-
Lưu Access Token trong Memory (biến state của React hoặc Redux/Zustand), hoặc nếu cần lưu tĩnh thì chỉ lưu ở
localStoragenhưng vòng đời Access Token phải cực kỳ ngắn (ví dụ: 5 - 15 phút).
Kết luận
Xử lý luồng Authentication chuẩn xác không chỉ giúp ứng dụng bảo mật hơn mà còn mang lại trải nghiệm liền mạch, chuyên nghiệp cho người dùng cuối (UX). Bằng cách áp dụng cơ chế Queue/Lock với Axios Interceptors và xây dựng một cấu trúc HttpClient rõ ràng, bạn sẽ hoàn toàn loại bỏ được nỗi ám ảnh "văng app" khi Token hết hạn.
Hãy thử áp dụng ngay kiến thức này vào module Authentication trong dự án thực tế của bạn để thấy rõ sự khác biệt!








