[P4] Nhập môn Authentication: Lưu JWT token ở local storage hay cookie?

[P4] Nhập môn Authentication: Lưu JWT token ở local storage hay cookie?

Đây là phần 4 trong series Giải ngố authentication. Nếu các bạn chưa xem phần 1,2,3 thì có thể xem ở đây:

Có rất nhiều tranh cãi xung quanh việc lưu token ở đâu? Có người lưu ở Local Storage, có người lưu ở Cookie, có người lưu ở Session Storage, có người lưu ở RAM, có người lưu ở IndexedDB, có người lưu ở WebSQL, có người lưu ở đâu đó khác nữa...

Vậy thực sự thì lưu ở đâu mới tốt?

Bài viết này mình chia sẻ dựa trên quan điểm cá nhân nên là có thể nó không hợp với dự án của bạn, nhưng mình sẽ cố gắng đưa ra các lập luận để bạn có thể tự đưa ra quyết định cho dự án của mình.

Oke bắt đầu luôn nhé 🤜


🥇 Tóm tắt về Access Token và Refresh Token

Bạn nào đã đọc bài viết [P3] Nhập môn Authentication: JWT thì sẽ biết công dụng của 2 loại JWT này

  • Access Token: Là một token có thời gian sống ngắn, được tạo ra bởi server, lưu ở client và client sẽ đính kèm nó vào HTTP request khi gửi request lên server, nhằm giúp server xác thực client.

  • Refresh Token: token có thời gian sống dài hơn, được lưu trong database ở server và lưu ở client, dùng để tạo ra access token mới mỗi khi access token hết hạn.


🥇 Lưu Access Token ở đâu?

Với đa số các dự án thì mình sẽ không chọn lưu ở Session Storage và Ram (lưu trong 1 biến của JavaScript) bởi vì

  • Nếu lưu ở Session Storage thì khi bạn đóng trình duyệt đi mở lại thì session storage sẽ bị xóa => Bạn sẽ phải đăng nhập lại.

  • Nếu lưu ở RAM thì bạn sẽ không thể chia sẻ access token giữa các tab trình duyệt được, cũng như đóng tab thì access token sẽ mất => Bạn sẽ phải đăng nhập lại

Rõ ràng UX trong 2 trường hợp này không tốt, trừ khi yêu cầu dự án của các bạn là như vậy.

🥈 Lưu ở Local Storage

Ưu điểm

  • Nhanh, tiện lợi, không cần phụ thuộc vào backend để lưu trữ.

  • Bộ nhớ khá lớn, thường là trên 5MB

  • Có thể tự quyết định request nào cần access token để gửi lên server, request nào không cần

  • Không tự động gửi access token lên server, nên nếu bị tấn công CSRF thì attacker không thể lấy được access token của bạn.

Nhược điểm

  • Nếu bị tấn công XSS thì attacker có thể lấy được access token

Một website có thể bị tấn công XSS từ khá là nhiều nguồn, ví dụ như: Do chính code chúng ta viết ra có lỗ hổng, do các thư viện bên thứ 3 như React, Vue, Lodash,...

Đây là cái lý do duy nhất mà một số người anti localstorage một cách cực đoan.

Ưu điểm

  • Không thể truy cập được từ Javascript nếu bạn set thuộc tính httpOnly, nên nếu có bị tấn công XSS thì cũng không lấy được token của bạn.

Nhược điểm

Có một cái nhược điểm đó là có thể bị tấn công CSRF, nhưng bạn có thể giải quyết bằng cách thêm một số thuộc tính cho cookie như sameSite, secure, domain, path để giảm thiểu khả năng bị tấn công CSRF.

Ngoài ra nếu dùng các framework SPA ngày nay nữa thì khả năng bị tấn công CSRF cũng không còn cao nữa. Vậy nên mình không cho đây là nhược điểm.

Vậy theo mình nhược điểm khi dùng Cookie là

  • Bạn không thể lấy được các payload của JWT token, vì JavaScript không truy cập được vào cookie nếu chúng ta set thuộc tính httpOnly cho cookie.

  • Bộ nhớ Cookie trên trình duyệt rất bé, loanh quanh 4KB thôi.

  • Dùng cookie thì phía backend sẽ phải xử lý thêm một số thứ như: parse cookie, set cookie, kiểm tra request đến server. Nếu đến từ browser thì parse cookie, nếu đến từ mobile app thì dùng header Authorization để lấy token...


🥇 Suy nghĩ của mình

Bạn thấy đấy, lưu ở đâu cũng có ưu nhược riêng.

🥈 Tại sao chúng ta không kết hợp cả 2 nhỉ?

Cookie đem lại ưu thế hơn 1 xíu về độ bảo mật khi so với local storage, nhưng cũng làm mất đi cái hay của JWT là có thể đọc được payload của JWT token ở client.

Có những trường hợp chúng ta cần đọc payload để biết thời gian hết hạn của token chẳng hạn, nhưng không lấy được access token ở trong cookie cũng khá là khó chịu.

Giải quyết vấn đề này thì chúng ta có thể chia access token làm 2 phần:

  • Header.Payload thì lưu ở local storage

  • Signature thì lưu ở cookie

Khi gửi lên server thì server sẽ ghép 2 phần này lại thành 1 và kiểm tra tính hợp lệ của token.

Như vậy thì client có thể đọc được payload JWT và cũng giữ lại ưu điểm của việc lưu ở Cookie.

🥈 Vậy thì không nên lưu token ở Local Storage à?

Đâu đó bạn sẽ gặp những bài như Please Stop Using Local Storage hoặc LocalStorage vs Cookies: All You Need To Know About Storing JWT Tokens Securely in The Front-End làm bạn hoang mang và có cái nhìn không tốt về Local Storage.

Nếu các bạn lướt xuống đọc comment các bài viết trên thì vẫn có rất nhiều ý kiến không đồng tình với tác giả.

Chúng ta cần làm rõ thế này, lưu trữ access token ở Cookie không giúp chúng ta tránh được tấn công XSS mà khi bị tấn công XSS thì hacker khó lấy được access token của bạn hơn thôi.

Nhiều người không hình dung ra được mức độ thiệt hại khi bị tấn công XSS nó lớn như thế nào.

Một website mà bị tấn công XSS nghĩa là web đấy toang, hacker có thể làm được nhiều việc nghiêm trọng hơn là lấy được access token của bạn. Ví dụ:

  • Điều khiển website của bạn để lừa người dùng gửi tiền vào tài khoản của hacker

  • Hiển thị popup yêu cầu người dùng nhập username/password để lấy thông tin người dùng

Vậy nên lưu token ở Local Storage mình thấy rất là bình thường, nó đem lại sự tiện lợi cho cả phía Front-End lẫn Back-End, không có vấn đề gì phải anti nó cả.

Muốn cân bằng giữa Cookie và Local Storage thì có thể kết hợp cả 2 như mình đã nói ở trên, rồi mã hóa thêm bằng một thuật toán nữa ở phía client cho tăng độ khó,...

Nói chung muốn bảo mật hơn thì có nhiều cách lắm, nhưng hãy nghĩ xem nó có thực sự cần thiết hay không, liệu nó có đáng để bỏ thời gian ra làm hay không.

À xíu nữa quên, nếu API chỉ nhận access token thông quan HTTP Header Authorization thì lại thêm 1 lý do nữa để chúng ta lưu token ở Local Storage rồi 😃


🥇 Tóm lại

  • XSS là game over, bất kể bạn lưu token ở đâu

  • Lưu token ở Local Storage hay Cookie đều ổn, không có gì phải anti cả.

  • Muốn cân bằng giữa ưu điểm của cả 2 thì có thể kết hợp cả 2.

  • Mã hóa thêm 1 vài bước ở client nếu muốn tăng độ bảo mật.

Nguồn bài viết: https://duthanhduoc.com

Phạm Hồng Đức
Phạm Hồng Đức
Một developer thích nghiên cứu và chia sẻ kiến thức lập trình, phần mềm mã nguồn mở (open-source), và cuộc sống.