Tìm hiểu về xác thực và phân quyền trong ứng dụng

Trong bài này, chúng ta sẽ cùng tìm hiểu về xác thực (authentication) và phân quyền (authorization) trong ứng dụng.

1. Xác thực (Authentication) là gì?

Theo định nghĩa của wikipedia, Xác thực/ Định danh (authentication) là một hành động nhằm thiết lập hoặc chứng thực một cái gì đó (hoặc một người nào đó) đáng tin cậy, có nghĩa là, những lời khai báo do người đó đưa ra hoặc về vật đó là sự thật.

Như đã biết, RESTful web service sử dụng HTTP protocol như một phương tiện giao tiếp và HTTP request là stateless protocol. Tức là server không lưu giữ bất kỳ thông tin nào của client, server xử lý các request một cách độc lập, không phụ thuộc vào trạng thái hay kết quả của request trước.

Như hình bên dưới, do server không lưu giữ bất kỳ thông tin nào của request trước. Nên mỗi request gửi lên server đều phải phải chứng thực lại, mặc dù là request của cùng một user đã được chứng thực.

Một trong những cách để giải quyết vấn đề này là mỗi request client gửi lên đều gửi kèm thông tin đã chứng thực trước đó.

2. Authentication được thực hiện như thế nào?

2.1. Dấu hiệu nhận biết

Một số dấu hiện nhận biết quá trình chứng thực có thể được thực hiện hay không là: ở mỗi request, client sẽ gửi kèm thông tin chứng thực lên server như username/ password, một chuỗi chứa thông tin mã hóa (token, api key), một chuỗi random (session_id). Chúng thường được gửi kèm với trong HTTP request như: query string trong URL, Header (Cookie header, Authorization header, Custom header), Body (Form field, Hidden field,…)

2.2. Quá trình authentication

Để có được dấu hiệu nhận dạng phía trên, ta cần có sự thống nhất trước giữa người dùng và ứng dụng để ứng dụng của chúng ta có thể nhận dạng được người dùng.

Về cơ bản thì một quá trình authentication sẽ gồm 2 bước:

  • Xác thực một user (thường là request đầu tiên).
  • Lưu giữ đăng nhập (cho các request phía sau).

Một quá trình authentication sẽ bao gồm 3 phần:

  • Sinh ra dấu hiệu: đây là việc chúng ta quyết định xem dùng dấu hiệu gì, tạo ra dấu hiệu đó như thế nào. Một quá trình authentication có thể có sự xuất hiện của nhiều dấu hiệu, ví dụ username/password, user token, api key,… Các dấu hiệu này sẽ có cách sinh ra khác nhau, quy ước sử dụng khác nhau.
  • Lưu trữ dấu hiệu: Đây là việc ứng dụng sẽ quyết định lưu trữ dấu hiệu này ở đâu, ở cả server và client, thông qua vị trí nào trên HTTP request,…
  • Kiểm tra dấu hiệu: Đây là việc ứng dụng của chúng ta kiểm tra lại tính hợp lệ của dấu hiệu, đối chiếu xem dấu hiệu này có hợp lệ hay không và của người dùng nào,…

Phía trên ảnh là ví dụ quá trình authentication, trong đó mỗi request tùy thuộc vào thông tin đầu vào sẽ được xử lý qua 1 hoặc nhiều phần của quá trình authentication.

3. Cơ chế lưu giữ đăng nhập người dùng

Trong bài viết này, chúng ta sẽ cùng tìm hiểu chủ yếu tới 3 cơ chế lưu giữ đăng nhập người dùng cơ bản là:

  • Basic Authentication
  • Session-based Authentication
  • Token-based Authentication

3.1. Basic Authentication

Basic Auth là cơ chế xác thực đơn giản nhất của một ứng dụng web. Cách hoạt động của Basic Auth là gửi chính username + password của người dùng theo mỗi request.

3.1.1. Flow

  • Dấu hiệu: Chuỗi username:password đã được mã hóa Base64. Ví dụ có username là abc, password là 123 thì ta tạo chuỗi mã hóa: abc:123 –Base64–> YWJjOjEyMw==
  • Lưu trữ dấu hiệu:
    • Tại server: Máy chủ web sẽ lưu lại username, password trong database, file (htpasswd),…
    • Tại client: Sau khi hỏi người dùng nhập username và password lần đầu, browser sẽ lưu lại 2 giá trị này trong bộ nhớ được quản lý bởi mỗi trình duyệt (và chúng ta không thể tiếp cận bộ nhớ này bằng code trên trang) để tránh phải liên tục hỏi chúng ta username, password. Tuy nhiên thời gian lưu thường là có giới hạn.
    • Truyền tải: chuỗi đã mã hóa base64 phía trên sẽ được truyền trong HTTP request trong Authorization header với từ khóa Basic phía trước: Authorization: Basic YWJjOjEyMw==
  • Kiểm tra dấu hiệu: Với mỗi request gửi lên kèm thông tin username/password trên, server sẽ so sánh username/password với database, config file,… để kiểm tra tính hợp lệ.

Flow của Basic Auth:

3.1.2. Usecase

Ưu điểm:

  • Đơn giản, do đó được hầu hết các trình duyệt, webserver (nginx, apache,…) hỗ trợ. Bạn có thể dễ dàng config cho webserver sử dụng Basic Auth với 1 vài dòng config.
  • Dễ dàng kết hợp với các phương pháp khác. Do đã được xử lý mặc định trên trình duyệt và webserver thông qua truyền tải http header, các bạn có thể dễ dàng kết hợp phương pháp này với các phương pháp sử dụng cookie, session, token,…

Nhược điểm:

  • Username/password dễ bị lộ. Do mỗi request đều phải truyền username và password nên sẽ tăng khả năng bị lộ qua việc bắt request, log server,…
  • Không thể logout. Vì việc lưu username, password dưới trình duyệt được thực hiện tự động và không có sự can thiệp của chủ trang web. Do vậy không có cách nào logout được người dùng ngoại trừ việc tự xóa lịch sử duyệt web hoặc hết thời gian lưu của trình duyệt.
  • Không thân thiện với người dùng. Việc hiển thị hộp thoại đăng nhập cũng như thông báo lỗi của trình duyệt, như các bạn đã biết là vô cùng nhàm chán, không chứa đựng nhiều thông tin cho người dùng.

Vì những đặc điểm trên, Basic Auth thường được sử dụng trong các ứng dụng nội bộcác thư mục cấm như hệ thống CMSmôi trường developmentdatabase admin,… lợi dụng việc chặt chẽ của kiểm tra Basic Auth trên các web server để tránh tiết lộ thông tin hệ thống nội bộ cho người ngoài, phòng chống hack, khai thác lỗ hổng ứng dụng,…

3.2. Session-based Authentication

Session-based authentication là cơ chế xác thực người dùng dựa trên việc tạo ra session của người dùng ở phía server. Sau quá trình xác thực người dùng thành công (username/password,…) thì phía server sẽ tạo và lưu ra một Session ID duy nhất để định danh. Session này chứa thông tin của người dùng đang đăng nhập và trả lại cho client session ID để truy cập session cho những request sau.

Phía client có thể lưu Session ID lại dưới dạng cookie và gửi kèm nó trong mọi request. Hệ thống sau đó sẽ dùng Session ID được gửi đi để xác định danh tính của user truy cập, để người dùng không cần phải nhập lại thông tin đăng nhập lần sau.

Khi Session ID được gửi lên, server sẽ xác định được danh tính của người dùng gắn với Session ID đó, đồng thời sẽ kiểm tra quyền của user xem có được truy cập tác vụ đó hay không. Giải pháp session và cookie vẫn có thể sử dụng, tuy nhiên ngày nay chúng ta có nhiều yêu cầu hơn, chẳng hạn như các ứng dụng Hybrid hoặc SPA (Single Page Application) có thể cần truy cập tới nhiều hệ thống backend khác nhau, vì vậy session và cookie lấy từ 1 server có thể không sử dụng được ở server khác.

3.2.1. Flow

Chúng ta hãy điểm qua các khía cạnh cơ bản của cơ chế này:

  • Dấu hiệu: 1 chuỗi (thường là random) unique gọi là Session ID
  • Lưu trữ dấu hiệu:
    • Tại server: Lưu dữ liệu của session trong database, file, ram,… và dùng Session ID để tìm kiếm.
    • Tại client: Lưu Session ID trong bộ nhớ cookie, hoặc URL trang web, form field ẩn,…
    • Truyền tải: Session ID sẽ xuất hiện trong các HTTP request tiếp theo trong Cookie (header Cookie: SESSION_ID=abc), URL (/profile?session_id=abc), body (form field ẩn),…
  • Kiểm tra dấu hiệu: Server dùng Session ID client truyền lên để tìm dữ liệu của session từ các nguồn lưu như database, file, ram,…

Quá trình set Session ID thường được thực hiện một cách tự động bởi server, cho nên session-based authentication thường sử dụng Cookie, vì cookie có thể set được từ phía server và được browser áp dụng tự động cho các request tiếp theo. Do đó cơ chế này thường đi liền với cookie. Tuy nhiên hãy nhớ là có nhiều cách để sử dụng được session ID mà không dùng cookie nữa nhé.

Flow của Session-based Authentication

3.2.2. Usecase

Ưu điểm:

  • Thông tin được giấu kín: Client chỉ được biết tới Session ID thường là 1 chuỗi random không mang thông tin gì của người dùng, còn mọi thông tin khác của phiên đăng nhập hay người dùng hiện tại đều được lưu phía server nên cơ chế này giữ kín được thông tin của người dùng trong quá trình truyền tải.
  • Dung lượng truyền tải nhỏ: Bởi vì tự thân Session ID không mang theo thông tin gì, thông thường chỉ là một chuỗi ký tự unique khoảng 20-50 ký tự, do vậy việc gắn Session ID vào mỗi request không làm tăng nhiều độ dài request, do đó việc truyền tải sẽ diễn ra dễ dàng hơn.
  • Không cần tác động client: Theo mình thì để sử dụng cơ chế session này bạn chủ yếu chỉ cần sửa phía server. Client mà cụ thể là browser hầu như không cần phải xử lý gì thêm bởi đã được tích hợp tự động (đối với cookie), hoặc response trả về của server đã có sẵn (đối với session ID ở URL hoặc hidden form)
  • Fully-controlled session: Tính chất này có thể cho phép hệ thống quản trị TẤT CẢ các hoạt động liên quan tới phiên đăng nhập của người dùng như thời gian login, force logout,…

Nhược điểm:

  • Chiếm nhiều bộ nhớ: Với mỗi phiên làm việc của user, server sẽ lại phải tạo ra một session và lưu vào bộ nhớ trên server. Số data này có thể còn lớn hơn cả user database của bạn do mỗi user có thể có vài session khác nhau. Do vậy việc tra cứu đối với các hệ thống lớn nhiều người dùng sẽ là vấn đề.
  • Khó scale: Vì tính chất stateful của việc lưu session data ở phía server, do đó bạn sẽ khó khăn hơn trong việc scale ngang ứng dụng, tức là nếu bạn chạy ứng dụng của bạn ở 10 máy chủ, hay 10 container, thì 1 là bạn phải dùng chung chỗ lưu session, 2 là nếu không dùng chung bộ nhớ session thì phải có giải pháp để ghi nhớ user đã kết nối tới server nào của bạn. Nếu không rất có thể chỉ cần ấn refresh thôi, user kết nối với server khác khi cân bằng tải là sẽ như chưa hề có cuộc login ngay.
  • Phụ thuộc domain: Vì thường sử dụng cookie, mà cookie lại phụ thuộc vào domain, do vậy khả năng sử dụng phiên đăng nhập của bạn sẽ bị giới hạn ở đúng domain được set cookie. Điều này không phù hợp với các hệ thống phân tán hoặc tích hợp vào ứng dụng bên thứ 3.
  • CSRF: Nói nôm na là Session ID thường được lưu vào Cookie, và cookie mới là thứ dễ bị tấn công kiểu này. Vì cookie được tự động gắn vào các request tới domain của bạn. Ví dụ:
    • User vừa login vào my-bank.com và được set cookie: session_id=123
    • User vào trang web taolahacker.com xem tut của mình
    • Trên taolahacker.com mình ngầm gửi 1 request ajax tới domain my-bank.com.
    • Vì browser tự động thêm cookie session_id=123 vào request ajax trên, do vậy request của mình có thể thao tác mọi thứ như User thật.

Vì những đặc điểm trên, Session-based Authentication thường được dùng trong các website và những ứng dụng web làm việc chủ yếu với browser, những hệ thống monolithic do cần sự tập trung trong việc lưu session data và sự hạn chế về domain.

3.3. Token-based Authentication

Token-based Authentication là cơ chế xác thực người dùng dựa trên việc tạo ra token – một chuỗi ký tự (thường được mã hóa) mang thông tin xác định người dùng được server tạo ra và lưu ở client. Server sau đó có thể không lưu lại token này.

3.3.1. Flow

  • Dấu hiệu: 1 chuỗi chứa thông tin người dùng (thường được mã hóa và signed) gọi là token
  • Lưu trữ dấu hiệu:
    • Tại server: Thường là không cần lưu.
    • Tại client: Ứng dụng client (javascript, mobile,…) phải tự lưu token trong các bộ nhớ ứng dụng, local storage, cookie,…
    • Truyền tải: Token sẽ xuất hiện trong các HTTP request tiếp theo trong Authorization header (Authorization: Bearer abc), Cookie (header Cookie: token=abc), URL (/profile?token=abc), body (ajax body, field),…
  • Kiểm tra dấu hiệu: Token thường có tính self-contained (như JWT), tức là có thể tự kiểm tra tính đúng đắn nhờ vào các thuật toán mã hóa và giải mã chỉ dựa vào thông tin trên token và 1 secret key nào đó của server. Do đó server không cần thiết phải lưu lại token, hay truy vấn thông tin user để xác nhận token.

JWT hay Json Web Token là một loại token được chấp nhận và sử dụng rộng rãi như một tiêu chuẩn của các nền tảng web hiện đại (RFC 7519) bởi nó thỏa mãn được tính chất self-contained, được hỗ trợ bởi nhiều ngôn ngữ và nền tảng và hơn hết là cấu trúc JSON đơn giản, nhỏ gọn hơn rất nhiều so với các loại token khác như Simple Web Tokens (SWT) and Security Assertion Markup Language Tokens (SAML). Chi tiết về JWT, chúng ta sẽ cùng tìm hiểu ở một bài viết khác.

Flow của Token-based Authentication:

3.3.2. Usecase

Ưu điểm:

  • Stateless: Bởi vì token thường có tính chất self-contained, do vậy server không cần lưu thêm thông tin gì về token hay map giữa token và người dùng. Do vậy đây là tính chất quan trọng nhất, phục vụ cho việc scale ứng dụng theo chiều ngang khi không cần quan tâm tới việc bạn sẽ sinh ra token ở đâu và verify token ở đâu.
  • Phù hợp với nhiều loại client: Nên nhớ, cookie là một concept được các browser áp dụng tự động, còn với các client sử dụng Web API như mobile, IoT device, server,… thì việc sử dụng cookie lại rất hạn chế. Sử dụng token trong header hay URL,… sẽ dễ dàng hơn cho client trong việc lưu lại token và truyền tải token.
  • Chống CSRF: Do việc sử dụng token phải được client xử lý từ việc lưu tới truyền tải, do vậy sử dụng token (mà không dùng cookie) sẽ phòng chống được các trường hợp tấn công như với trường hợp session/cookie.
  • Không bị giới hạn bởi domain: Đây là tính chất giúp các hệ thống hiện đại có sự tham gia của bên thứ 3 hoạt động dễ dàng hơn khi không bị giới hạn chỉ ở domain của hệ thống đăng nhập.

Nhược điểm:

  • Khó quản lý đăng xuất: Bởi vì server không lưu thông tin gì về token hay session của user, do đó điều khó kiểm soát nhất chính là việc đăng xuất. Và vì việc kiểm tra token chỉ dựa vào thông tin trên token, do vậy sẽ khó để ứng dụng của chúng ta vô hiệu hóa một token vẫn còn hiệu lực.
  • Phức tạp phần client: Cơ chế sử dụng token thường yêu cầu client phải có xử lý liên quan tới lưu token, gửi token, do vậy sẽ không phù hợp với những website kiểu cũ, sử dụng nhiều server render html và phần javascript hạn chế.
  • Thông tin dễ lộ: Khác với session, thông tin về phiên đăng nhập của người dùng có trên token và được lưu phía client, do vậy sẽ có các nguy cơ liên quan tới lộ thông tin trong token trong quá trình lưu trữ, truyền tải,… Chính vì vậy, thông thường người ta chỉ lưu 1 số thông tin thiết yếu như user_id, username mà không lưu những thông tin nhạy cảm như password vào token.
  • Dung lượng truyền tải lớn: Thường thì 1 token sẽ dài hơn session ID khá nhiều, mà token lại được gửi với mỗi request, do vậy độ dài request sẽ tăng lên, do đó băng thông truyền tải cũng sẽ cần phải tăng theo. Tuy nhiên, đây chỉ là một điểm hạn chế nhỏ so với những lợi ích nó mang lại.

Vì các đặc điểm trên, Token-based Authentication thường được sử dụng trong các hệ thống Web API, các hệ thống phân tán, micro-services, các hệ thống có sự tham gia của các nền tảng khác như mobile, IoT, server,…, hoặc các website kiểu mới (phân tách rõ UI app và API).

3.4. Bảng so sánh

Sau đây là bảng so sánh để các bạn dễ hình dung về 3 cơ chế đăng nhập này:

ĐẶC ĐIỂMBASICSESSION-BASEDTOKEN-BASED
Dấu hiệuusername + passwordChuỗi random (Session ID)Chuỗi mang thông tin được mã hóa
Truyền tảiAuthorization HeaderHeader (cookie) / URL / Body (form)Header (Auth, custom) / URL / Body
Lưu ServerKhông lưu (vì chính là UserDB)Có lưu Session Data (memory, database, file,…)Không lưu (vì token chứa đủ thông tin rồi)
Lưu ClientBrowser tự lưu (username + pass)Cookie (Session ID)Local storage, Cookie, session storage (browser)
Cách verifySo sánh với User trong databaseDùng Session ID để tìm data trong session storageKiểm tra tính toàn vẹn của token qua signature của token
Phù hợp choHệ thống internalMonolithic websiteWeb API của hệ thống phân tán, đa nền tảng,…

4. Phân quyền (Authorization) là gì?

Sau khi đã có định danh và giao thức dùng để giao tiếp, câu hỏi tiếp theo là cần trả lời câu hỏi đối tượng với định danh đó có quyền thực hiện 1 hành động, truy cập 1 tài nguyên nào đó hay không. Quá trình này gọi là phân quyền (Authorization). Authorization xảy ra sau khi hệ thống của bạn được Authentication (xác thực) thành công.

Xác thực (authentication) trả lời câu hỏi “bạn là ai?” và phân quyền (authorization) sẽ trả lời câu hỏi “bạn có thể làm được gì?” Hai câu hỏi này luôn là thành phần không thể thiếu của mọi hệ thống, nhưng mức độ áp dụng thì lại tùy thuộc vào từng giai đoạn. Nếu bạn làm mọi thứ chặt chẽ ngay từ đầu, nó có thể làm tăng độ phức tạp và làm chậm sự phát triển của công ty. Nhưng nếu bạn làm nó quá muộn, thì có thể bạn sẽ hứng chịu nguy cơ bị tấn công và rủi ro từ đó.

5. So sánh Authentication với Authorization

AuthenticationAuthorization
Authentication xác nhận danh tính của user để cấp quyền truy cập vào hệ thống.Authorization xác định xem user có được phép truy cập tài nguyên không.
Đây là quá trình xác nhận thông tin đăng nhập để có quyền truy cập của người dùng.Đó là quá trình xác minh xem có cho phép truy cập hay không.
Nó quyết định liệu người dùng có phải là những gì anh ta tuyên bố hay không.Nó xác định những gì người dùng có thể và không thể truy cập.
Authentication thường yêu cầu tên người dùng và mật khẩu.Các yếu tố xác thực cần thiết để authorization có thể khác nhau, tùy thuộc vào mức độ bảo mật.
Authentication là bước đầu tiên của authorization vì vậy luôn luôn đến trước.Authorization được thực hiện sau khi authentication thành công.
Ví dụ, sinh viên của một trường đại học cụ thể được yêu cầu tự xác thực trước khi truy cập vào liên kết sinh viên của trang web chính thức của trường đại học. Điều này được gọi là authentication.Ví dụ, authorization xác định chính xác thông tin nào sinh viên được phép truy cập trên trang web của trường đại học sau khi authentication thành công.