Tiếp nối sự thành công của [P1] Giải ngố authentication: Basic Authentication, phần 2 này chúng ta sẽ đi tìm hiểu về Cookie và Session là gì. Cách áp dụng Cookie và Session trong việc xác thực người dùng.
Trong bài viết này cũng như cả series này thì mình đều dùng Nodejs để ví dụ demo nhé. Các bạn học PHP, Java, .Net thì nó cũng có những thứ tương tự thôi.
Đây là những kiến thức mà các bạn sẽ đạt được sau khi đọc xong bài này
Oke, đầu tiên thì chúng ta tìm hiểu cookie nhé ✊
🥇Cookie là gì?
Cookie là một file nhỏ được lưu trữ trên thiết bị user. Cookie thường được dùng để lưu thông tin về người dùng website như: tên, địa chỉ, giỏ hàng, lịch sử truy cập, mật khẩu (à dù có thể lưu mật khẩu được nhưng đừng lưu nhé, lỡ bị hacker hack, nó lấy đc mật khẩu là toang đấy!)…
Cookie được ghi và đọc theo domain.
Ví dụ khi bạn truy cập vào website cá nhân của Được https://duthanhduoc.com, và server mình trả về cookie thì trình duyệt của bạn sẽ lưu cookie cho domain duthanhduoc.com.
Khi bạn gửi request đến https://duthanhduoc.com (bao gồm việc bạn enter url vào thanh địa chỉ hay gửi api đến) thì trình duyệt của bạn tìm kiếm có cookie nào của https://duthanhduoc.com không và gửi lên server https://duthanhduoc.com.
Nhưng nếu bạn truy cập vào https://google.com thì google sẽ không đọc được cookie bên https://duthanhduoc.com, vì trình duyệt không gửi lên.
☠️ Lưu ý: Nếu bạn đang ở trang https://google.com và gửi request đến https://duthanhduoc.com thì trình duyệt sẽ tự động gửi cookie của https://duthanhduoc.com lên server của https://duthanhduoc.com, đây là một lỗ hổng để hacker tấn công CSRF. Để tìm hiểu thêm về kỹ thuật tấn công và cách khắc phục thì các bạn đọc thêm ở những phần dưới nhé.
Một website có thể lưu nhiều cookie khác nhau, ví dụ profile, cart, history, …
Bộ nhớ của cookie có giới hạn, nên bạn không nên lưu quá nhiều thông tin vào cookie. Thường thì một website chỉ nên lưu tối đa 50 cookie và tổng cộng kích thước của các cookie trên website đó không nên vượt quá 4KB.
Đến đây mình sẽ làm rõ một số vấn đề về cookie như sau
🥈Cookie được lưu trữ ở đâu?
Nó lưu trong 1 cái file, file này thì được lưu ở trên ổ cứng của bạn. Vậy nên là bạn tắt trình duyệt, shutdown máy tính đi mở lại thì nó vẫn còn đấy.
Ví dụ Truy cập file cookie trên macOS
Trên macOS, các file cookie được lưu trữ trong thư mục của trình duyệt web bạn đang sử dụng. Cụ thể:
Thường thì không ai vào đây xem đâu, vì nó là file nhị phân, bạn không thể đọc được nó. Chúng ta sẽ dùng trình duyệt để xem nhé.
🥈Làm sao để ghi dữ liệu lên cookie của trình duyệt?
Có 3 cách để ghi dữ liệu lên cookie
🥈Làm sao để đọc dữ liệu từ cookie?
Khi bạn truy cập vào 1 url hoặc gọi 1 api, trình duyệt sẽ tự động gửi cookie lên server. Nhớ là tự động luôn nha, bạn không cần làm gì cả.
Ngoài ra bạn có thể dùng Javascript để đọc cookie của bạn, ví dụ
Lưu ý là nếu cookie được set HttpOnly thì bạn không thể đọc được cookie bằng Javascript đâu nhé.
Hoặc mở devtool lên xem 😂
Lưu ý là cookie lưu ở trang nào thì trình duyệt sẽ gửi cookie trang đó lên server nha. Nếu cookie của https://facebook.com thì không có chuyện bạn vào https://duthanhduoc.com và mình đọc được cookie facebook của bạn đâu.
🥈Demo ví dụ Cookie bằng NodeJs
Nãy giờ nắm đủ lý thuyết rồi hen, giờ demo thực hành thôi.
Dưới đây là đoạn code Node.js để tạo 1 cookie, khi bạn chạy đoạn code Node.Js này lên, truy cập vào http://localhost:3000/set-cookie thì trình duyệt sẽ lưu cookie với tên username và giá trị John Doe trong 1 giờ (3600000 ms).
Khi bạn truy cập vào http://localhost:3000/get-cookie thì trình duyệt sẽ gửi cookie lên server, server sẽ đọc cookie và trả về trang tương ứng cho bạn.
Đoạn code trên các bạn có thể test bằng axios gọi API với method GET nhé. Chứ nhiều bạn nghĩ là cookie chỉ dùng cho server side rendering truyền thống, không áp dụng được cho RESTful API thì toang 😂, nó vẫn dùng bình thường nhá.
🥈Một số lưu ý quan trọng khi sử dụng cookie
🥈HttpOnly
Khi set HttpOnly cho một cookie của bạn thì cookie đó sẽ không thể đọc được bằng Javascript (tức là không thể lấy cookie bằng document.cookie được). Điều này giúp tránh được tấn công XSS.
Tân công XSS hiểu đơn giản là người khác có thể chạy được code javascript của họ trên trang web của bạn. Ví dụ bạn dùng một thư viện trên npm, người tạo thư viện này cố tình chèn một đoạn code javascript như sau
Khi bạn deploy website, user truy cập vào website của bạn, thì đoạn code trên sẽ chạy và gửi cookie của user về cho kẻ tấn công (người tạo thư viện). Nếu cookie chứa các thông tin quan trọng như tài khoản ngân hàng, mật khẩu, … thì user đã bị hack rồi.
Để set HttpOnly cho cookie, bạn chỉ cần thêm option httpOnly: true vào cookie như sau
🥈Secure
Khi set Secure cho một cookie của bạn thì cookie đó chỉ được gửi lên server khi bạn truy cập vào trang web bằng https. Điều này giúp tránh được các lỗ hổng MITM (Man in the middle attack).
Man-in-the-middle (MITM) là một kỹ thuật tấn công mạng, trong đó kẻ tấn công can thiệp vào kết nối giữa hai bên và trộn lẫn thông tin giữa họ. Khi bị tấn công, người dùng thường không nhận ra được sự can thiệp này. Ví dụ bạn dùng wifi công cộng, kẻ tấn công có thể đọc được dữ liệu bạn gửi đi.
Để set Secure cho cookie, bạn chỉ cần thêm option secure: true vào cookie như sau
🥈Tấn công CSRF
Lợi dụng cơ chế khi request trên một url nào đó, trình duyệt sẽ tự động gửi cookie lên server, kẻ tấn công có thể tạo một trang web giả mạo, khi user truy cập vào trang web giả mạo và thực hiện hành động nào đó, trình duyệt sẽ tự động gửi cookie lên server, kẻ tấn công có thể lợi dụng cookie này để thực hiện các hành động độc hại.
Ví dụ:
Chúng ta dùng facebook để post bài lên newfeeeds tại url là https://facebook.com. Tất nhiên là muốn post được bài viết thì facebook sẽ kiểm tra bạn đã đăng nhập hay chưa thông qua cookie bạn gửi lên lúc bạn nhấn nút Đăng bài.
Dưới đây là đoạn code mô phỏng cách server facebook hoạt động khi bạn nhấn nút Đăng bài.
(Mô phỏng lỗi thôi chứ facebook không có lỗi này đâu nhé 😂)
File server facebook.js chạy tại http://localhost:3000
File server hacker.js chạy tại http://http://127.0.0.1:3300 (sở dĩ mình không để link localhost vì nó sẽ cũng domain với cái server facebook trên, nên chúng ta sẽ không test được)
🥉Demo tấn công CSRF
Đây là hướng dẫn tấn công CSRF dựa trên ví dụ ngay bên trên nha.
🥈Cách phòng chống tấn công CSRF
Ở đây mình sẽ có một số cách phòng chống như sau
✅Cách 1: Sử dụng thuộc tính SameSite=Strict cho cookie
Các bạn mở lại code server facebook.js và sửa lại như thế này nhé
Với SameSite=Strict thì cookie sẽ không được gửi đi nếu request không phải là request từ trang web hiện tại. Ví dụ như ở trên thì cookie sẽ không được gửi đi nếu request đến từ http://127.0.0.1:3300
Lưu ý với SameSite
Quy tắc quyết định 2 site có phải là same không nó phức tạp hơn bạn nghĩ.
Ví dụ như 2 site https://edu.duthanhduoc.com và http://duthanhduoc.com được coi là same site vì cùng public suffix duthanhduoc.com
Nhưng 2 site https://duthanhduoc.github.io và https://dtd.github.io thì không được coi là same site vì khác public suffix, ở đây các bạn có thể hiểu github.io nó giống như cái tên miền com rồi.
Để hiểu rõ hơn về samesite thì mình khuyên các bạn nên đọc những bài này
✅Cách 2: Sử dụng CSRF Token:
CSRF token là một chuỗi ngẫu nhiên được tạo ra để bảo vệ khỏi tấn công Cross-Site Request Forgery (CSRF). Khi người dùng yêu cầu truy cập tài nguyên, server sẽ tạo ra một token và gửi nó về cho người dùng. Khi người dùng gửi yêu cầu tiếp theo, họ phải bao gồm token này trong yêu cầu của mình. Nếu token không hợp lệ, yêu cầu sẽ bị từ chối. Điều này giúp ngăn chặn kẻ tấn công thực hiện các yêu cầu giả mạo.
Để áp dụng CSRF Token cho facebook.js server thì các bạn sửa code thành như dưới đây
✅Cách 3: Sử dụng CORS:
Cross-Origin Resource Sharing (CORS) là một cơ chế để ngăn chặn các yêu cầu từ các tên miền khác nhau. Bằng cách thiết lập CORS, bạn có thể chỉ cho phép các yêu cầu từ các tên miền cụ thể hoặc từ tất cả các tên miền. Ví dụ như ở trên thì nếu server facebook chỉ cho phép các yêu cầu từ tên miền http://localhost:3000 thì hacker sẽ không thể tấn công được.
Thêm cái này vào facebook.js
🥈Single Page Application có bị tấn công CSRF không?
Câu trả lời là có! Nhưng hiếm khi xảy ra trừ khi bạn chủ động set SameSite=None cho cookie của bạn.
Như các bạn thấy thì CSRF nghĩa là một request được thực hiện trên một trang web hacker. Nãy giờ chúng ta chỉ ví dụ với cơ chế GET POST truyền thống, chứ không phải REST API phổ biến như chúng ta thao tác ngày nay.
Với REST API thì để gửi một request đến http://localhost:3000/status trên trang web http://127.0.0.1:3300 chúng ta có thể dùng fetch API như dưới đây.
Lúc này cookie của http://localhost:3000 sẽ không được gửi lên http://localhost:3000/status đâu, vì nếu các bạn không set SameSite khi server trả về thì mặc định trình duyệt sẽ ngầm hiểu đây là SameSite=Lax.
Mà với SameSite=Lax thì chỉ cho phép gửi cookie đối với những request mà reload lại page (ví dụ request trong form method post truyền thống ở các ví dụ trên), còn mấy cái fetch, XMLHttpRequest hay axios thì nó không gửi cookie đâu.
Còn nếu bạn set SameSite=none (khi đó phải thêm secure=true nữa browsers nó mới chập nhận cái samesite none này) thì khỏi nói luôn, hacker có thể thay đổi data của bạn nếu bạn truy cập trang web của hacker.
🥈Tóm lại thì làm sao bảo vệ website khỏi CSRF?
Nếu bạn không dùng cookie thì không cần quan tâm, vì no cookie no CSRF.
Nếu bạn sài combo REST API và SPA thì đầu tiên là phải thiết lập cors, httpOnly=true, secure=true, SameSite=Strict hoặc SameSite=Lax.
Cẩn thận với SameSite=Strict:
Vì nếu bạn set SameSite=Strict thì khi bạn đăng nhập vào example.com rồi. Bây giờ bạn click vào đường link example.com trên trang web khác thì trình duyệt sẽ không gửi cookie đâu, dẫn đến việc dù bạn đã đăng nhập lúc nãy nhưng vẫn bị chuyển về trang login vì bị cho là chưa đăng nhập.
Cái này thường xảy ra khi website của bạn là website theo MPA truyền thống, còn nếu là SPA thì không sao cả, vì hầu như các SPA chúng ta đều gọi request và gửi cookie lên server thông qua fetch hay XMLHttpRequest (tức là đã redirect đến trang) chứ không phải ngay khi click vào đường link.
Cá nhân mình nghĩ không cần phải dùng thêm CSRF token nữa, vì nó chỉ làm cho cơ chế xác thực của bạn phức tạp hơn thôi. Như trên là đủ rồi.
🥇Session Authentication
Oke, mọi người đã nắm vững cookie chưa nhỉ 🤪? Nếu chưa thì đọc lại phần trên đi nhé.
Trước khi tìm hiểu về session authentication thì chúng ta cần phải hiểu về session trước đã.
🥈Session là gì?
Session là phiên lưu trữ trên server để quản lý thông tin liên quan đến mỗi người dùng trong quá trình tương tác với ứng dụng.
Session được lưu trữ trên server, còn cookie được lưu trữ trên client. Nhớ rõ điều này nha.
Session có thể được lưu ở dạng file, database, cache, memory, … tùy vào cách thiết kế server như thế nào.
🥈Session Authentication là gì?
Session Authentication là một cơ chế xác thực người dùng bằng cách sử dụng session.
Khi người dùng đăng nhập thành công, server sẽ tạo ra một session mới và gửi session id đó về cho client thông qua cookie (thường là cookie thôi chứ không nhất thiết, client có thể lưu vào local storage cũng được). Client sẽ gửi nó lên server mỗi khi thực hiện một request. Server kiểm tra session id này có tồn tại hay không, nếu có thì xác thực thành công, không thì xác thực thất bại.
🥈Flow hoạt động của Session Authentication
Hình dưới đây là luồng hoạt động của phương pháp xác thực bằng session.
🥈Code mô phỏng Session Authentication
Dươi đây là code mô phỏng session authentication bằng express.js node.js
🥈Ưu nhược điểm của Session Authentication
🥉Ưu điểm
🥉Nhược điểm
Scale ngang là chúng ta mở rộng quy mô hệ thống bằng cách thêm các server mới vào hệ thống, thay vì nâng cấp server hiện tại lên một cấu hình cao hơn.
Scale dọc là chúng ta mở rộng quy mô hệ thống bằng cách nâng cấp server hiện tại lên một cấu hình cao hơn.
🥇Đón chờ phần kế tiếp
Phần 2 này dài gấp 3 lần phần 1 😂, mong rằng qua bài này mình đã giúp các bạn hiểu rõ hơn về Cookie cũng như Session là gì, quan trọng hơn là cơ chế Session Authentication hoạt động như thế nào.
Phần 3 mình sẽ giới thiệu với các bạn về một cơ chế xác thực người dùng khác, đó là JWT Authentication. Cơ chế này sẽ giúp chúng ta giải quyết được một số nhược điểm của Session Authentication.
Hẹn gặp lại các bạn ở phần 3 nhé 😘