Xem phần 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?.
Ở bài viết trước 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 gì nữa khi cơ hội đánh cắp cả hai là như nhau?
Để tìm ra câu trả lời cho vấn đề trên, có rất nhiều cuộc tranh luận đã được nổ ra và dường như chưa có hồi kết bởi vì ai cũng có ý đúng. Tôi đóng vai trò như là một khán giả ngồi trên khán đài của sân bóng, tiếp thu ý kiến của họ để từ đó tổng hợp lại và đưa ra câu trả lời mà tôi thấy là tâm đắc nhất. Dĩ nhiên nó không hoàn toàn chính xác và tôi hy vọng bạn đọc tìm ra được điều bất hợp lý trong này.
Câu trả lời cuối cùng của tôi là "trong trình duyệt". Tùy thuộc vào mức độ bảo mật mà bạn hướng tới để lựa chọn ra phương án phù hợp nhất giúp tiết kiệm thời gian và cả chi phí. Bởi bên trong trình duyệt cung cấp một số cách lưu trữ như là Cookie, localStorage, sessionStorage hay thậm chí có thêm một cách lưu trữ phía máy chủ nữa là Session. Mỗi cách đều có logic nghiệp vụ riêng, việc khéo léo tổ chức lưu trữ Access Token và Refresh Token trong đó giúp giảm thiểu được phần nào tính nghiêm trọng của cuộc tấn công XSS.
Một sự thật là nếu trang web của bạn bị tấn công XSS thì phải nói rằng dù có lưu trữ ở đâu thì khả năng Access Token và Refresh Token bị rò rỉ là rất lớn. Để cho dễ hình dung, hãy mổ xẻ bản chất của cuộc tấn công XSS. Tấn công XSS xảy ra khi kẻ tấn công có thể chạy mã JavaScript trên trang web của bạn. Điều này có nghĩa là kẻ tấn công có thể lấy mã thông báo truy cập mà bạn đã lưu trữ bất kì đâu mà mã Javascript có quyền truy cập. Một cuộc tấn công XSS có thể xảy ra từ mã Javascript của bên thứ ba có trong trang web của bạn như React, Vue, jQuery, Google Analytics, v.v... Những thư viện càng phổ biến càng là tâm điểm của sự chú ý, một khi chúng bị tấn công thì thiệt hại phải nói là vô cùng lớn.
Rõ ràng là việc không sử dụng bất kì một thư viện của bên thứ ba nào trong trang web của bạn là rất khó. Mỗi thư viện được thêm vào đồng nghĩa với rủi ro tăng lên. Kẻ tấn công cũng có thể khai thác XSS dựa vào các form/input thông tin trên trang web của bạn. Ví dụ tính năng chat công khai, kẻ tấn công có thể nhập vào một đoạn mã Javascript đánh cắp dữ liệu. Nếu không xử lý đúng cách phần hiển thị nội dung chat, những người dùng khác khi truy cập vào rất dễ bị dính mã độc. Một cuộc tấn công dò tìm MIME cũng có khả năng gây ra một cuộc tấn công XSS.
Song song với XSS là một cuộc tấn công CSRF (Cross Site Request Forgery) nhằm vào Access Token. Nó được hiểu là một cuộc tấn công buộc người dùng phải thực hiện một yêu cầu ngoài ý muốn. Kẻ tấn công không trực tiếp đánh cắp Access Token hay Refresh Token mà chúng muốn lợi dụng Access Token được đính kèm lên máy chủ trong mỗi request của bạn.
Ví dụ trang web cho phép thay đổi email người dùng thông qua API:
POST /email/change HTTP/1.1
Host: yoursite.com
Content-Type: application/x-www-form-urlencoded
Cookie: sessionID=qwerty
[email protected]
Kẻ tấn công tạo một form tự động gửi yêu cầu POST
đến /email/change
đính kèm email của hắn. Lúc đó session sẽ được tự động thêm vào yêu cầu vì bạn đang trực tiếp truy cập vào trang web, chỉ có điều bạn không biết sự có mặt của form ẩn đó. Tuy nhiên, điều này có thể được giảm thiểu bằng cách sử dụng `sameSite`.
Từ đó suy ra nếu muốn không bị rò rỉ Access Token và Refresh Token thì phải lưu trữ chúng ở nơi mà mã Javascript không có quyền truy cập. Trong trình duyệt có nơi nào như thế không? Xin thưa là có đấy, đó chính là `httpOnly` cookie. Nhưng liệu httpOnly
có phải là phương án cuối cùng? Trước khi trả lời, tôi muốn các bạn hiểu rõ hơn về các cơ chế lưu trữ phổ biến trong trình duyệt và cả máy chủ.
Session là một trong những cách xác định phiên người dùng phổ biến nhất của trang web server side render. Cách thức hoạt động khá dễ hiểu, server sinh ra một session ID cho request yêu cầu xác định phiên, trình duyệt lưu lại ID đó và gửi kèm lên trong tất cả các request sau đó.
Phương pháp này yêu cầu máy chủ phải lưu trữ và quản lý phiên của từng máy khách. Session ID thông thường sẽ được client lưu trữ trong Cookie hoặc tham số trong URL. Điều này vẫn khiến nó có thể bị đánh cắp thông qua một cuộc tấn công XSS.
Không cần phải nói nhiều nữa, Cookie là thông tin được lưu trữ trên máy tính của bạn bởi một trang web bạn truy cập. Sử dụng Cookie để lưu lại thông tin bạn cần trên một trang web như thông tin người dùng đăng nhập... Có thể nói Cookie là một trong những phương pháp lâu đời nhất được sử dụng nhằm mục đích lưu trữ này. Những thông tin được lưu trữ trong Cookie được gửi lên máy chủ thông qua headers của request nhờ đó máy chủ có thể xác định được phiên của người dùng.
Javascript có quyền truy cập vào Cookie thế nên chúng vẫn có thể bị đánh cắp bởi một cuộc tấn công XSS.
Tuy nhiên, đối với Cookie có một khái niệm là httpOnly
. Nếu 1 cookie được chỉ định thêm tùy chọn httpOnly
thì nó miễn nhiễm với Javascript. Nghĩa là Javascript không có quyền truy cập vào httpOnly
mà cookie đó tự động được gửi lên trong mỗi yêu cầu đến máy chủ.
Đây đều là hai cách lưu trữ thông tin với dung lượng lớn hơn Cookie rất nhiều, chính vì thế chúng thường được dùng để lưu lại thông tin có kích thước lớn hoặc là bộ nhớ cache cho các tính năng của trang web để chúng chạy mượt mà hơn.
Thông tin trên localStorage được lưu trữ bền vững cho đến khi bạn xóa chúng. Ngược lại, sessionStorage thường sẽ bị xóa ngay sau khi bạn kết thúc phiên làm việc với trang web (tắt tab, tắt trình duyệt...).
Javascript có quyền truy cập vào cả hai thế nên chúng vẫn có thể bị đánh cắp bởi một cuộc tấn công XSS.
Từ đó suy ra những cách khác nhau mà bạn có thể lưu trữ:
httpOnly
cookie -> dễ bị CSRF nhưng có thể tránh được. Tốt hơn so với phương án đầu tiên.httpOnly
cookie -> an toàn khỏi CSRF. Mặc dù Access Token có thể bị đánh cắp tuy nhiên chúng chỉ tồn tại trong thời gian ngắn nên cũng có thể giảm thiểu được mức độ rủi ro.Có thể thấy cách thứ ba tỏ ra được ưu thế hơn cả hai cách đầu. Cách để thiết lập không quá phức tạp. Chỉ cần lúc đăng nhập hay ủy quyền thì Access Token được trả về trong body response còn Refresh Token chỉ được trả về thông qua httpOnly
cookie.
Trên đây là cách lưu trữ để hạn chế tối đa việc Access Token và Refresh Token bị rò rỉ. Trong trường hợp Access Token và Refresh Token bị rò rỉ thì có cách nào để phát hiện hoặc ngăn chặn không? Tôi sẽ tiếp tục có bài viết trong series này, cảm ơn các bạn đã theo dõi.
Bí mật ngăn xếp của Blog
Là một lập trình viên, bạn có tò mò về bí mật công nghệ hay những khoản nợ kỹ thuật về trang blog này? Tất cả bí mật sẽ được bật mí ngay bài viết dưới đây. Còn chờ đợi gì nữa, hãy bấm vào ngay!
Đăng ký nhận thông báo bài viết mới
Xin chào, tôi tên là Hoài - một anh Dev kể chuyện bằng cách viết ✍️ và làm sản phẩm 🚀. Với nhiều năm kinh nghiệm lập trình, tôi đã đóng góp một phần công sức cho nhiều sản phẩm mang lại giá trị cho người dùng tại nơi đang làm việc, cũng như cho chính bản thân. Sở thích của tôi là đọc, viết, nghiên cứu... Tôi tạo ra trang Blog này với sứ mệnh mang đến những bài viết chất lượng cho độc giả của 2coffee.dev.Hãy theo dõi tôi qua các kênh LinkedIn, Facebook, Instagram, Telegram.
Bình luận (4)