Nếu các bạn muốn xem thông tin cá nhân thì làm theo bước sau: B1: Vào app -> Http -> Controllers -> UserController B2:Trong function __construct() xóa dòng lệnh $this->middleware('can:user'); Show Nhưng hầu hết các dự án ở công ty lúc mình join thì đều được dựng sẵn base và platform, hoặc là bê dự án phase 1 ở 1 nơi nào đó về làm tiếp các giai đoạn tiếp theo, do đó mình không có cơ hội chọt vào phần quản lý authentication và token lắm. Ơ nói thế thì không có nghĩa là mình không làm được, mình có nghiên cứu và áp dụng vào dự án riêng, dự án freelancer, và cả làm boilerplate cho các assignment phỏng vấn nữa 😝 Chưa kể lúc lên công ty thì mình cũng muốn đưa kiến thức research, đọc được lên chém gió cùng đồng nghiệp. Cái topic liên quan JWT này cũng được mình discuss chém gió đâu đó gần 2 năm trước với 1 người anh, nay đã qua Sing làm việc. JWT luôn là một phương pháp phổ biến trong việc authenticate user cho các ứng dụng web. Nhưng câu hỏi đặt ra là, chúng ta nên lưu JWT token ở đâu? Ưu nhược điểm của từng nơi lưu như thế nào? Làm cách nào để chúng ta cải thiện nó? First thing firstMình là Kiên, developer với một tay nghề thập cẩm, từ .NET tới Java, Angular, PHP, và gần đây nhất là Golang, cái gì mình cũng chọt vào 1 chút, dạo này thì focus hơn vào backend. Again, mình là một chú developer thích viết lách, cũng đã khá lâu kể từ bài blog cuối cùng, hôm nay mình sẽ khởi động lại bằng topic “Lưu access token ở đâu thì hợp lý?” Các phương thức authenticationChúng ta đảo qua một chút về các phương thức xác thực trước,
Với phương thức session & cookie, thì dĩ nhiên trình duyệt auto nhận authentication data và lưu vào cookie rồi, vậy còn JWT chúng ta lưu ở đâu? JWT (JSON Web Token) như tên gọi, là token được lưu dưới dạng text, có thể parse qua JSON và chứa những thông tin liên quan để authenticate. Mổ xẻ JWTJWT gồm 3 phần: Header, Payload và Signature.
Chúng ta có thể gán thêm information cần thiết vào phần payload bên cạnh những "Claim" cơ bản được định nghĩa trong RFC 7519. Muốn giấu thông tin khỏi JWT thì làm sao?Bạn có thể sẽ thắc mắc là một vài thông tin như username, email có thể bị lộ từ việc decode JWT. Vì nó chỉ được Encode chứ không được Encrypt. Để giấu thông tin khỏi JWT, bạn có thể mã hóa thông tin trước khi đưa vào JWT. Tuy nhiên, JWT thường được sử dụng để xác thực người dùng và không được mã hóa. Thay vào đó, nó được “sign” (ký) để đảm bảo tính toàn vẹn của dữ liệu. (Tham khảo bài viết về HTTP và HTTPS để tìm hiểu thêm về chữ ký nhé). Bạn cũng có thể tham khảo thêm chi tiết tại RFC-7516 về việc mã hóa các trường trong JWT. Tuy nhiên, hãy nhớ rằng việc mã hóa trong JWT có thể làm tăng độ phức tạp và làm chậm việc xác thực người dùng. Tham khảo về encrypt - decrypt cho ai quan tâm: https://advancedweb.hu/how-to-sign-verify-and-encrypt-jwts-in-node/ Vậy bây giờ lưu access token ở đâu?Chúng ta có thể bỏ qua luôn session storage và cả application state, vì chúng quá mỏng manh yếu đuối, đóng tab hoặc ctrl + F5 cái là đi mất rồi. Local StorageĐây chính là 1 nơi nguy hiểm, bởi vì local storage có thể được truy cập từ bất kỳ script nào chạy trên cùng 1 domain (và session storage cũng thế nha). Chúng ta gọi đó là lỗ hổng XSS, tức Cross-Site Scripting, kẻ xấu chỉ cần inject script vào target là đã có thể lấy được token. Điểm chí mạng thứ 2 đó chính là local storage không có thời gian expire, nó sẽ còn sống mãi. Bình thường con người sống lâu thì mừng, còn token sống lâu thì lại là vấn đề 🃏 CookieMột cách khác chính là lưu vào cookie, mặc dù cookie vẫn có thể truy cập từ script trong cùng domain bằng cách gọi
0 . Nhưng nó vẫn an toàn hơn localstorage, tại sao? Thứ nhất, cookie có thời gian expiration, không sống thọ như local storage. Thứ 2, cookie có 1 flag gọi là
1. Flag này sẽ giúp ngăn chặn cookie truy cập bởi client side. HttpOnly flag in cookie settings Lúc này khi các bạn truy cập
0 nó chỉ trả về empty mà thôi. Khoan! Nếu bật HttpOnly thì cookie cũng sẽ bị dính chưởng lời nguyền CSRF (Cross-Site Request Forgery) CSRF tức là request được gửi đi sẽ không phải gửi từ domain đã đăng nhập, mà là 1 domain lạ nào đó. Ví dụ bạn đã đăng nhập facebook, sau đó nhờ bấm vào 1 link "nóng bỏng" share hàng nào đó trên bảng tin, bạn truy cập trang giả mạo
3. Trang này sẽ inject 1 đoạn script gửi request tới
4, request này sẽ auto được lấy cookie của
5 từ browser. Để thoát khỏi CSRF, chúng ta cần thêm 2 flag nữa, chính là
6 , flag này chỉ cho phép gửi cookie đi trên cùng 1 domain đã set mà thôi. Mà để bật SameSite strict, thì chúng ta cũng phải bật luôn flag
7, flag này chỉ cho phép gửi cookie đi qua kết nối HTTPS. Xem thêm tại MDN - Samesite cookie flag Nhược điểm của việc đặt same-site chính là không bật được tính năng cross-site như social login các thứ cần kết nối tới 1 service 3rd party. Tất cả biện pháp trên cũng không đảm bảo 100% tránh được CSRF, cho nên chúng ta vẫn cần implement 1 cơ chế nào đó để tránh CSRF phía server. Tổng kết lại 1 chút nàoLocalStorage Cookies XSS Có Nếu set flag HttpOnly thì tránh được XSS. Expiration Không Có CSRF Không Bị dính CSRF, mặc dù nếu set
6 thì sẽ đỡ nguy cơ nhưng vẫn phải implement cơ chế chống CSRF phía server. Túm cái váy lại thì cách an toàn nhất để lưu token vẫn là đưa vào cookie và set một vài flag, tuy nhiên tùy vào nghiệp vụ dự án thì chúng ta có thể chọn phương án localstorage. Implement với CookieCách set HttpOnly phía serverTất nhiên rồi, khi bạn bật mode HttpOnly, thì client sẽ không có quyền truy cập vào cookie, do đó chúng ta phải thực hiện việc set cookie thông qua backend. Với nodejs, chuyện này khá dễ:
Tương tự cho Go với Gin:
Client gửi token đi thế nào?Bạn có thể tự hỏi, làm sao client có thể lấy được token, trong khi cookie đã không còn xuất hiện trong con mắt của client script nữa? Đáp án là header này, hãy thêm nó vào middleware gửi request phía client.
Khi header này được set true, trình duyệt sẽ tự động gửi bất kỳ cookie nào có thuộc tính HttpOnly mà có domain là domain đang gửi request tới. (Tham khảo tại MDN) Với axios, chúng ta có thể enable bằng cách set
9:
Nhưng ở đây lại đẻ ra 1 của nợ nữa, đó chính là CORS, ví dụ bạn đăng nhập và lưu cookie ở
0, nhưng lại cần gửi request tới
1 thì dễ bị chặn lắm. Thì chúng ta tiếp tục phải enable CORS phía server. Enable CORSCORS - Cross-Origin Resource Sharing, là một tính năng bảo mật của trình duyệt giúp ngăn chặn hoặc giới hạn những resource được cho phép theo origin (field này trình duyệt tự thêm vô). Trình duyệt sẽ tự động thêm field origin vào Flow nôm na là nếu server đánh dấu origin là được phép (thông qua header), thì trình duyệt sẽ gửi request đi, còn nếu server không cho, thì browser sẽ chặn request lại. Do đó mà dev có thể bypass bằng 1 extension của trình duyệt 😜, tuy nhiên ai lại bắt user đi cài extension bao giờ, do đó ta phải implement nó. Nodejs:
Như code trên chúng ta sẽ allow 2 origin được gửi request tới chính là domainA và domainB. Không khuyến khích dùng pattern
2 Với Go Gin, thì có thể tham khảo lib này nhé Chống CSRFKĩ thuật này khá đơn giản, với mỗi form request, chúng ta generate 1 csrf token dựa vào session hiện tại, client gán nó vào 1 hidden input, và server sẽ xử lý field này bằng 1 thuật toán nào đó, nếu hợp lệ thì cho qua. Ví dụ code ở client:
Hầu hết các framework đều support CSRF rồi, xưa mình code laravel PHP thì nó support middleware có sẵn dễ như ăn bánh. Với Go, tham khảo thư viện gin-crsf để thêm 1 middleware:
Tổng kếtHoooo, mình không nghĩ là bài này lại dài tới vậy, cứ nghĩ là viết trong 1 buổi tối, mà phải tới trưa ngày hôm sau mới xong. Hi vọng sau bài này bạn sẽ có 1 cái nhìn tổng quan về JWT authenticaion và những vấn đề bên cạnh như CORS và bảo mật các request với CSRF. Happy coding! FAQsSau khi up bài này thì có vẻ anh em dev có vài câu hỏi liên quan, mình đã trả lời trong comment (bên Viblo), và mình tổng hợp lại ở đây luôn: |