Cách bảo mật JWT Tokens: Hướng dẫn các thực hành tốt nhất
JSON Web Tokens (JWT) đã trở thành tiêu chuẩn ngành cho xác thực phi trạng thái. Chúng cho phép máy chủ xác minh danh tính người dùng mà không cần lưu trữ dữ liệu phiên trên chính máy chủ. Tuy nhiên, bản chất phi trạng thái này giới thiệu các rủi ro bảo mật cụ thể nếu không được triển khai chính xác. Trong hướng dẫn này, chúng ta sẽ khám phá cách bảo mật JWT tokens một cách chính xác để bảo vệ dữ liệu người dùng và duy trì tính toàn vẹn của ứng dụng.
Hiểu về cấu trúc JWT
Trước khi đi sâu vào các biện pháp bảo mật, việc hiểu rõ ba phần của JWT là điều kiện tiên quyết: Header, Payload và Signature.
- Header: Chứa thuật toán được sử dụng để ký token (ví dụ: HS256, RS256).
- Payload: Chứa các claim (dữ liệu về người dùng, như ID và vai trò).
- Signature: Đảm bảo token không bị can thiệp.
Vì payload được mã hóa base64, nên nó có thể đọc được bởi bất kỳ ai chặn được token. Đây là lý do tại sao các biện pháp bảo mật như HTTPS và kiểm tra chữ ký là bắt buộc.
1. Luôn sử dụng HTTPS
Bước cơ bản nhất trong việc bảo mật bất kỳ token nào là đảm bảo nó được truyền qua một kênh an toàn. Nếu một JWT được gửi qua HTTP, một kẻ tấn công trên mạng có thể chặn và đọc payload của token, tiềm năng trích xuất thông tin nhạy cảm của người dùng.
BBVA nhấn mạnh việc sử dụng HTTPS là bắt buộc để ngăn chặn các cuộc tấn công trung gian (man-in-the-middle). Việc mã hóa này đảm bảo token vẫn giữ bí mật từ lúc rời khỏi client đến lúc đến server.
2. Bảo mật bí mật ký (Signing Secrets)
Chữ ký là phần duy nhất của JWT chứng minh tính xác thực của token. Để tạo chữ ký hợp lệ, bạn cần một bí mật khóa (cho thuật toán đối xứng) hoặc khóa riêng (cho thuật toán đối xứng).
Không mã cứng (Hardcode) bí mật
Một trong những lỗi phổ biến nhất mà các nhà phát triển gặp phải là mã cứng khóa bí mật trực tiếp vào mã nguồn. Nếu mã của bạn được đẩy lên kho lưu trữ công cộng (như GitHub), bí mật sẽ bị lộ. H2S Media khuyên không nên thực hiện thực hành này, thay vào đó khuyên sử dụng biến môi trường để lưu trữ bí mật. Điều này giúp dữ liệu nhạy cảm tách biệt khỏi logic ứng dụng của bạn và ngăn chặn việc lộ ra vô tình.
Luân phiên (Rotate) bí mật thường xuyên
Dù bí mật của bạn được lưu trữ an toàn, chúng cũng nên được luân phiên định kỳ. Nếu một bí mật bị lộ, kẻ tấn công có thể forge (tạo giả) token mãi mãi. Việc luân phiên thường xuyên giới hạn cửa sổ cơ hội cho kẻ tấn công.
3. Lựa chọn cơ chế lưu trữ phù hợp
Nơi bạn lưu trữ JWT trên phía client là một quyết định bảo mật quan trọng. Hai tùy chọn chính là LocalStorage và Cookie HttpOnly.
Rủi ro của LocalStorage
Lưu trữ JWT trong localStorage khiến chúng có thể truy cập bởi bất kỳ JavaScript nào chạy trên trang. Nếu ứng dụng của bạn có lỗ hổng Cross-Site Scripting (XSS), kẻ tấn công có thể dễ dàng đánh cắp token và impersonate (mạo danh) người dùng. MakeUseOf nhấn mạnh rằng dù localStorage tiện lợi, nhưng nó lại dễ bị tấn công XSS.
Sự an toàn của Cookie HttpOnly
Cookie HttpOnly không thể truy cập qua JavaScript. Điều này khiến chúng miễn nhiễm với các cuộc tấn công XSS cố gắng đánh cắp token. Để tối đa hóa sự an toàn, đặc biệt là trong các ứng dụng xử lý dữ liệu nhạy cảm, việc lưu trữ JWT trong cookie HttpOnly là cách tiếp cận được khuyến nghị. Phương pháp này cũng giúp giảm thiểu các cuộc tấn công CSRF (Cross-Site Request Forgery) nếu kết hợp với các thuộc tính SameSite phù hợp.
4. Triển khai thời gian hết hạn ngắn
JWT được thiết kế để là phi trạng thái, nghĩa là chúng hợp lệ cho đến khi hết hạn. Tuy nhiên, token càng có thời gian hiệu lực lâu, rủi ro càng cao nếu nó bị chặn. AWS khuyến nghị sử dụng thời gian hết hạn ngắn cho các token truy cập. Bằng cách giữ TTL (Time To Live) ngắn, bạn giới hạn thiệt hại mà kẻ tấn công có thể gây ra nếu họ quản lý đánh cắp được token.
Mẫu Refresh Token
Để duy trì trải nghiệm người dùng liền mạch mà không làm tăng rủi ro bảo mật, các nhà phát triển thường sử dụng kết hợp giữa token truy cập có thời gian sống ngắn và token refresh có thời gian sống lâu hơn. Token truy cập cấp quyền truy cập ngay lập tức vào API, trong khi token refresh được sử dụng để lấy token truy cập mới. Token refresh nên được lưu trữ an toàn, thường trong cookie HttpOnly, và có thời gian hết hạn dài hơn.
5. Xử lý việc thu hồi token
Một trong những thách thức inherent của JWT là chúng là phi trạng thái. Một khi đã phát hành, một token hợp lệ vẫn hợp lệ cho đến khi hết hạn, ngay cả khi người dùng đăng xuất hoặc thay đổi mật khẩu. Điều này làm cho việc thu hồi trở nên khó khăn.
TTL ngắn như một chiến lược thu hồi
Như đã đề cập, cách hiệu quả nhất để xử lý việc thu hồi là giữ thời gian hết hạn rất ngắn. Nếu một token bị lộ, kẻ tấn công chỉ có một thời gian giới hạn để sử dụng nó.
Danh sách đen (Blacklists)
Đối với các ứng dụng yêu cầu thu hồi ngay lập tức, bạn có thể triển khai một danh sách đen có trạng thái. Khi người dùng đăng xuất hoặc phiên của họ bị vô hiệu hóa, bạn thêm ID của token (JTI) vào cơ sở dữ liệu hoặc bộ nhớ đệm. Trước khi xác thực một token, máy chủ kiểm tra xem JTI có nằm trong danh sách đen hay không. Tuy nhiên, phương pháp tiếp cận này yêu cầu duy trì trạng thái, điều này mâu thuẫn với bản chất phi trạng thái của JWT.
6. Xác thực các claim của token
Khi nhận được một JWT, máy chủ của bạn phải thực hiện xác thực nghiêm ngặt. AWS chi tiết tầm quan trọng của việc xác thực Issuer (iss), Audience (aud) và Expiration (exp) claims.
- Issuer: Đảm bảo token được phát hành bởi ứng dụng của bạn.
- Audience: Đảm bảo token được dành cho API của bạn.
- Expiration: Luôn kiểm tra claim
expđể đảm bảo token chưa hết hạn.
Việc không xác thực các claim này có thể dẫn đến việc vượt qua phân quyền (authorization bypasses), nơi người dùng có thể truy cập các tài nguyên mà họ không nên có.
7. Xem xét các lựa chọn thay thế như PASETO
Mặc dù JWT được sử dụng rộng rãi, chúng có lịch sử các lỗ hổng bảo mật. Ví dụ, lỗ hổng thuật toán "none" cho phép kẻ tấn công tạo giả token. HackerNoon thảo luận về sự trỗi dậy của PASETO (Platform for Authentication, Security, and Encryption of Tokens), một tiêu chuẩn token mới hơn được thiết kế để giải quyết các lỗ hổng của JWT. PASETO hỗ trợ cả khóa đối xứng và đối xứng và được thiết kế để an toàn hơn theo mặc định. Mặc dù JWT vẫn là tiêu chuẩn, việc hiểu các lựa chọn thay thế như PASETO là quan trọng đối với các kiến trúc sư đưa ra quyết định dài hạn.
Câu hỏi thường gặp
Sự khác biệt giữa HS256 và RS256 là gì?
HS256 (HMAC với SHA-256) sử dụng một khóa bí mật chung giữa client và server. RS256 (RSA với SHA-256) sử dụng khóa riêng để ký token và khóa công khai để xác thực nó. RS256 thường được coi là an toàn hơn cho hệ thống phân tán vì khóa riêng không bao giờ rời khỏi server.
Tôi có thể thu hồi JWT ngay lập tức không?
Vì JWT là phi trạng thái, bạn không thể kỹ thuật thu hồi JWT ngay lập tức mà không duy trì một danh sách đen hoặc sử dụng thời gian hết hạn ngắn. Giải pháp thực tế nhất là sử dụng token truy cập có thời gian sống ngắn.
LocalStorage có an toàn cho JWT không?
Không, LocalStorage dễ bị tấn công XSS. Nếu ứng dụng của bạn bị lộ, kẻ tấn công có thể đọc LocalStorage và đánh cắp JWT. Cookie HttpOnly là cơ chế lưu trữ được khuyến nghị cho mục đích bảo mật.
Kết luận
Việc bảo mật JWT yêu cầu một tiếp cận đa lớp. Bằng cách sử dụng HTTPS, quản lý bí mật an toàn, lưu trữ token trong cookie HttpOnly, triển khai thời gian hết hạn ngắn và xác thực các claim nghiêm ngặt, bạn có thể giảm đáng kể rủi ro bị đánh cắp và lạm dụng token. Khi landscape của bảo mật web phát triển, việc cập nhật thông tin về các thực hành tốt nhất và các lựa chọn thay thế như PASETO là thiết yếu để duy trì một tư thế bảo mật vững mạnh.
Tài liệu tham khảo
- JWT vs PASETO: What's the Best Tool for Generating Secure Tokens?
- How to Build a Secure Nest.js REST API Using JWT and MongoDB
- How to secure API Gateway HTTP endpoints with JWT authorizer
- Best of 2021 – How to Revoke JSON Web Tokens (JWTs)
- JSON Web tokens (JWT): how to use them safely
- How to Create a JWT Secret in Node.js (Best Practices + Examples)
- How to monitor, optimize, and secure Amazon Cognito machine-to-machine authorization