Web System Authentication

Authentication Jun 25, 2021

Overview

Hôm nay chúng ta lại nói về Authentication và Authorization trong Web system. Với các bạn làm Web thì 2 khái niệm này không còn lạ lẫm gì nữa :

  • Authentication xác nhận danh tính của user
  • Authorization xác các quyền mà users đó được phép truy cập đối với các tài nguyên.
Authentication vs Authorizations

Nhắc đến authentication, tất cả chúng ta đều ngay lập tức liên tưởng đến hình thức xác thực cổ điển nhất : User/Password. Tuy nhiên với sự phát triển của công nghệ thì rất nhiều thính thức xác thực mới đã ra đời như :

  • Xác thực sinh học : vân tay, mống mắt
  • Xác thực qua ứng dụng bên thứ 3.

Tuy nhiên về cơ bản thì authentication sẽ dựa trên thông tin đăng nhập cá nhân ( credentials) mà bạn cung cấp, xác thực xem bạn có đúng là cá nhân đã được đăng ký với hệ thống hay không trước khi cho bạn tiếp cận với các tài nguyên.

Verify your credentials

Web System Authentication Flow

Chúng ta sẽ không nói về các hệ thống bảo mật sinh trắc hay các hệ thống khoá mã phức tạp vì nó nằm ngoài phạm vi bài chia sẻ này. Chúng ta chỉ cùng nhau tìm hiểu quá trình đăng nhập một hệ thống web điển hình, và xem ra quá trình này khá đơn giản :

  • Step 1 : Tất nhiên là đăng ký 1 tài khoản ( và có thể bạn sẽ cần kích hoạt tài khoản của mình trước khi vào step 2 )
  • Step 2 : Tai màn hình login ta cung cấp thông tin đăng nhập từ bước đăng ký Step 1 bao gồm : user / password
  • Step 3 : Click vào button login và thế là một màn hình dành riêng nào đó sẽ hiển thị ra

Với người dùng thông thường thì quả thật quá trình diễn ra đơn giản chỉ có vậy. Nhưng một web developer thì họ biết rằng không chỉ đơn giản như thế khi một loạt câu hỏi sẽ cần có lời giải đáp

  • Làm thế nào để duy trì trạng thái đăng nhập? Hay sau khi đã đăng nhập thành công người dùng sẽ không cần đăng nhập lại.
  • Làm thế nào để có thể đồng thời đăng nhập trên nhiều thiết bị
  • Làm thế nào để có thể logout khỏi một thiết bị mà không ảnh hưởng tới các thiết bị khác
  • Làm thế nào đảm bảo an toàn phiên đăng nhập của tôi, để không ai có thể giả dạng tôi thực hiện các hành vi mà tôi không chấp thuận.
  • v..vv

Behind the scene

Authentication rất đơn giản và được support sẵn bởi rất đa phần các framework nên cũng ít người tìm hiểu về những hoạt động phía sau bức màn, tuy nhiên hiểu thêm về cách thức client và được xác nhận định danh sẽ cho các bạn thêm một số hiểu biết khá thú vị.

Chúng ta đang nói về ứng dụng web, mà nói về web thì chắc chắn là nói về giao thức web HTTP. Ai cũng biết giao thức HTTP là stateless, tức là giao thức này không yêu cầu server phải lưu giữ lại thông tin hay trạng thái về request của user. Điều đó có nghĩa là tại một thời điểm nào đó, client có thể gửi một request lên server, server xử lý và trả lại kết quả sau đó kết nối của client và server sẽ kết thúc. Mỗi request sẽ độc lập với với các request trước đó và sau đó.

HTTP is a stateless protocol.

Dễ thấy rằng điều này đặt ra câu hỏi Làm thế nào để server có thể "ghi nhớ" user này đã login và cho phép thực hiện  truy cập vào tài nguyên của hệ thống. Giải pháp hiển nhiên cho câu hỏi này là cần phải lưu trữ trạng thái đã xác thực người dùng và gửi nó cùng với mỗi requests từ client lên server. Câu hỏi tiếp theo đặt ra là Bên nào sẽ là bên lưu trữ thông tin xác thực người dùng? Client hay server ? Dùng cái nào tốt hơn trong những hoàn cảnh nào? Có 2 cách implementation mà ai cũng biết đó là :

  • Session based authentication
  • Token based authentication

Và chúng ta sẽ cùng nhau tìm hiểu qua từng cách cũng như điểm mạnh điểm yếu của nó.

Session based authentication

Session (thông tin phiên làm việc) là cách làm phổ biến nhất và xuất hiện có lẽ là sớm nhất để giải quyết bài toán trao đổi thông tin xác thực giữa client và server.

Một session sẽ được sử dụng để lưu trữ tạm thời thông tin trên server để có thể được sử dụng cross nhiều pages của website. Một session chứa thông tin một phiên làm việc của một client, trong đó có thể chứa nhiều thông tin khác chứ không phải chỉ thông tin xác nhận người dùng.

Một session được khởi tạo khi client login vào hệ thống và sẽ kết thúc khi người đó logout khỏi hệ thống.  

Source : https://sherryhsu.medium.com/session-vs-token-based-authentication-11a6c5ac45e4

Từ sơ đồ phía trên ta thấy:

  • Một thông tin phiên làm việc được tạo ra khi login thành công. Điều này cho phép mỗi một phiên làm việc sẽ gắn với một client đã được định danh.
  • Thông tin về phiên làm việc được lưu trên server và server sẽ trả về sessionID cho client thay vì bản thân toàn bộ thông tin của phiên làm việc.
  • Client sẽ gửi các request tiếp theo lên server cùng với sessionID như sự chứng thực về định danh của mình.
  • Khi server nhận được request từ client, bằng cách sử dụng sessionID nhận được server sẽ có thể lấy được thông tin phiên làm việc qua đó xác nhận định danh của client.

Một số điểm mở rộng có thể bạn sẽ quan tâm :

  • Mỗi phiên làm việc thực tế sẽ lưu nhiều thông tin về user dưới dạng key-value.  
  • Các thông tin này do được lưu trữ trên server nên có thể được mã hoá / giải mã chỉ bởi server và làm tăng độ an toàn dữ liệu.
  • Có một vài lựa chọn để lưu thông tin session khi phát triển hệ thống

     –  File system : đây là cách lưu trữ đơn giản nhất. Một file sẽ được tạo ra để lưu trữ thông tin mỗi session. Việc tìm kiếm, đọc thông tin sessions sử dụng sessionID sẽ sử dụng đến các thao tác file I/O. File I/O thường là thao tác khá chậm nên session file không phải là lựa chọn tốt cho các hệ thống lớn. Ngoài ra sẽ gặp vấn đề đồng bộ session files trong bài toán horizontal  scaling ( mở rộng năng lực xử lý bằng cách thêm nhiều server )

     –  Rational Database : Để giải quyết bài toán về horizontal scaling, session sẽ cần được lưu tập trung tách biệt khỏi các server. Một center database là sự lựa chọn phù hợp để giải quyết vấn đề này. Lúc này, thông tin session sẽ được quản lý trong một bảng của database. Thao tác tìm kiếm, lưu trữ sẽ được thực hiện qua các câu query sử dụng DB Engine

     –  In-Memory database: Đối với các hệ thống với số lượng người sử dụng lớn thì các yêu cầu truy suất nhanh thông tin phiên làm việc có thể dẫn đến sự quá tải của một database thông thường. Để giải quyết vấn đề này chúng ta có thể cân nhắc thay thế DB server thông thường bởi một In-memory DB như Redis hay Memcache.

  • Thông tin trả về client là sessionID sẽ thường được set vào cookies của client và cookies chứa sessionID sẽ được gửi cùng các request tiếp theo để xác nhận định danh của client. Tuy nhiên đây không phải là cách làm duy nhất, có một số cách làm ít phổ biến hơn như dưới đây thường để đối ứng với trường hợp client không hỗ trợ sử dụng cookies.

     –  Trả về trong hidden form field

     – Trả về qua URL parameter

Một số điểm yếu của việc sử dụng session based authentication

  • Dễ bị tổn thương bởi tấn công CSRF. Chúng ta cần CSRF token như là một phương pháp bảo vệ ( VD : như trong blade của laravel là phải dùng CSRF token)
  • Việc cần lưu trữ session data trong database hoặc lưu trữ trong bộ nhớ server làm giảm khả năng scalable và tăng tải khi site có nhiều người dùng.
  • Việc sử dụng cookies chỉ có tác dụng trên một domain và thường bị disable cho trường hợp cross-domain bởi browser. Sẽ không khả thi để đọc và gửi cookies từ 1 domain a.com đến một domain b.com. Đây sẽ là vấn đề gặp phải khi API service có domain khác với domain của web và mobile.
  • Client cần gửi cookie đối với mọi request bao gồm cả nhưng request tới URL mà không cần authentication.
  • Server phải xử lý toàn bộ việc kiểm tra thông tin session sử dụng sessionID đối với mọi request nên tải toàn bộ đặt lên vai server.

Token based authentication

Để khắc phục các điểm yếu của session,đã xuất hiện một cách làm khác gọi thay vì sử dụng session ta sử dung token. Điểm khác biệt chính yếu ở đây là thông tin xác thực client thay vì được lưu trữ trên server sẽ được lưu trữ tại client. Thông tin đó được gọi với cái tên Access Token hay gọi ngắn gọn là Token

Token là một đoạn mã hoá nhỏ chứa một lượng lớn thông tin. Thông tin về user, permission, groups, .. được nhúng vào trong 1 token và được trả về từ server cho thiết bị của người dùng ( Okta.com )
Source : https://sherryhsu.medium.com/session-vs-token-based-authentication-11a6c5ac45e4

Từ sơ đồ trên ta thấy sau khi login thành công:

  • Server thay vì tạo ra session và trả lại client sessionID thì sẽ sinh ra Token ( JWT - JSON WEB TOKEN).
  • Server không lưu trữ token mà trả lại cho client
  • Client sẽ sử dụng token như một tham số trong Authorization Header cho các request đến tài nguyên được bảo vệ bởi server
  • Server sẽ thực hiện kiểm tra Token để quyết định có cho phép truy cập các resource được bảo vệ hay không.

Có thể thấy có 2 câu hỏi thú vị đặt ra :

Làm thế nào để Server verify được token từ client lên có hợp lệ không ? Vì nó đâu có lưu trữ bất cứ thông tin nào về token mà nó đã tạo.

     – Câu trả lời cho câu hỏi này khá đơn giản. Server thực hiện việc mã hoá thông tin theo cách chỉ nó có thể giải mã và xác nhận tính toàn vẹn thành token rồi trả về cho client. Việc này đảm bảo rằng khi token quay trở lại, server biết rằng token này do nó tạo ra và chưa hề bị sửa đổi. Các thuật toán sử dụng có thể là mã hoá đối xứng (symmetric algorithm) với secret key hoặc mã hoá bất đối xứng( asymmetric algorithm ) với cặp public key/private key.

Mã hoá đối xử sử dụng Shared Key 

Mã hoá bất đối xứng sử dụng cặp public key / private key 

     – Bạn có thể thắc mắc, tôi đâu có chia sẻ cặp public/private key của tôi với ai đâu nhỉ? Khi làm chức năng authentication tôi vẫn giữ cặp key này trên server đó chứ. Thật ra nếu bạn để ý thì với token based authentication, thì server của bạn vừa là sender nhưng cũng vừa là receiver, client có thể nhận token nhưng rồi cũng là chuyển về cho server để khẳng định việc đó nên không cần thiết phải công khai public key của bạn với client.

Làm thế nào mà token có thể chứa các thông tin về người dùng, quyền, nhóm vv .. Làm như vậy có an toàn không khi mà token có thể dễ dàng bị lấy mất ( do được lưu ở client ) và ai đó có có được thông tin nhạy cảm được nhúng trong token.
  • Nếu token chỉ là 1 chuỗi mã vô nghĩa ( dummy token ) với mục đích duy nhất là để server nhận diện ra client này đã được login thành công thông qua việc xác nhận token được sinh ra bởi chính nó thì chúng ta đã mất đi 1/2 ý nghĩa của việc xác thực danh tính. Bởi lẽ lúc này token không giúp trả lời được câu hỏi tôi là ai. Để giải quyết vấn đề này đương nhiên chúng ta có thể nghĩ đến việc lưu trữ token vào bảng nào đó và gắn liền với user id để từ đó sau khi xác nhận token hợp lệ thì tiến hành truy xuất thông tin người dùng, tuy nhiên cách làm này sẽ lại làm nảy sinh các điểm yếu giống của session, hơn nữa cả client và server sẽ đều phải lưu trạng thái.
  • Chúng ta có một sự lựa chọn tốt hơn, đó là ngay tại thời điểm tạo ra token (khi login thành công) , ngay lập tức server sẽ lấy thông tin liên quan đến người dùng ( các claims ) rồi nhúng vào token, mã hoá và gửi về cho client. Bằng cách làm này, với các request sau cho dù không lưu trạng thái server hoàn toàn có thể lấy được các thông tin cần thiết từ chính token sau khi thực hiện verify token không bị thay đổi, giải mã và tin tưởng sử dụng các thông tin trong đó. Ví dụ về một cấu trúc một JWT sẽ gồm 3 phần :
Header:  Thông tin về loại token và thuật toán được sử dụng để mã hoá. Thông tin này được mã hoá Base64 để tạo thành phần header của token
{
  "alg": "HS256",
  "typ": "JWT"
}
Payload: Thông tin về user, bao gồm quyền, thời gian hết hạn ...hay còn gọi là các claims. Có 3 loại claims : registered, public, và private claims. Thông tin này được mã hoã Base64 để tạo lên phần Payload của token.
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}
Signature: Chữ ký dùng để đảm bảo tính toàn vẹn của token. Chữ ký thường được hash và rất khó để hack và thay thế.  Ví dụ dưới đây ta dùng thuật toán mã hoá đối xứng với secret key để tạo ra phần signature của token từ phần header và payload đã được mã hoá Base64.
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)
  • Như vậy có thể thấy rằng nếu ăn trộm được một access token thì không khó khăn ta có thể đọc được thông tin trong payload vì nó chỉ là mã hoá Base64 thông qua một tools đơn giản như : https://jwt.io/#debugger-io. Chính vì vậy các thông tin đặt trong payload không nên là các thông tin nhạy cảm như password ngân hàng chẳng hạn mà chỉ nên là các thông tin cho phép định danh user ví dụ như user_id, email ...v..v
  • Tuy nhiên có thể thấy do có signature nên rất khó cho ai đó có thể fake token của client để thực hiện các hành vi ác ý. Nhưng nếu bị dò rỉ một access token thì người tấn công có thể hoàn toàn thực hiện các thao tác không mong muốn núp dưới danh nghĩa của bạn. Thế nên các access token thường rất nhanh chóng bị hết hạn để đảm bảo sự an toàn. Tuy nhiên nếu như vậy người dùng sẽ liên tục phải login mỗi khi access token hết hạn, để giải quyết vấn đề này server sẽ tiến hành tạo ra một refresh token. Refresh token có thời gian hết hạn lâu hơn và được sử dụng để lấy access token mới mỗi khi nó bị hết hạn. Refresh token chính vì vậy cần được đảm bảo an toàn vì nếu attacker có được họ sẽ có thể tạo ra access token để thực hiện hành vi tấn công cho đến khi refresh token bị server đưa vào black list hoặc bị hết hạn.
Access Token nếu được lưu vào local storage sẽ khiến cho nó dễ bị tổn thương bởi tấn công XSS
Access Token nếu được server trả về trong HttpOnly cookies cho client sẽ dễ bị tổn thương bở tấn công CSRF và sẽ ít bị ảnh hưởng bởi tấn công XSS
Refresh Token nếu được server trả về cho client trong HttpOnly cookie sẽ an toàn khỏi tấn côn CSRF và ít bị ảnh hưởng bởi tấn công XSS

Điểm mạnh của token-based authentication

  • Token based authentication là stateless, server sẽ không cần lưu thông tin về token vì bản thân nó đã chứa thông tin bao gồm cả các thông tin cho phép validate chính nó và thông tin liên quan đến định danh user. Server chỉ cần sign khi login thành công và thực hiện verify token trong request là hợp lệ hay không. Việc này làm giảm tải của server đáng kể trong môi trường nhiều người sử dụng.
  • Token based authentication với CORS enable cho phép bộc lộ APIs để sử dụng bởi các services và domain khác. Tức là API có thể phục vụ cả web và mobile platform, cũng như dễ implement hơn.
  • Dữ liệu được lưu trong token, nghĩa là nó có thể chứa bất cứ loại dữ liệu nào cho phép tính linh động của việc gửi kèm các thông tin trong token.
  • Do là stateless nên token-based authentication cung cấp sự dễ dàng cho việc thực hiện horizontally scale trong các hệ phân tán.

Tuy vậy token-based authentication cũng có những điểm yếu

  • Thông tin chứa trong token khiến kích thước token có thể lớn và ảnh hưởng đến tốc độ request.
  • Việc lưu trữ token ở client cũng lắm phiền toái. Nếu lưu vào cookies thì không  hiệu quả khi kích thước token lớn ( cookies có giới hạn về kích thước ); nếu lưu vào session storage thì close trình duyệt sẽ bị xoá mất, còn nếu lưu vào local storage thì bị ràng buộc với một domain nhất định
  • Token lưu ở client cũng dễ tổn thương bở tấn công XSS

Kết luận

Nói chung mỗi phương pháp đều có điểm mạnh, điểm yếu. Nắm được điểm mạnh và điểm hạn chế của mỗi phương pháp và yêu cầu của hệ thống sẽ giúp việc sử dụng đem lại hiệu quả lớn nhất.



Tags

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.