oauth-2.jpg

Mở đầu

Tôi xin mở đầu bài viết này bằng một câu chuyện về thực trạng về các ứng dụng hiện nay. Vấn đề đặt ra đó là số lượng ứng dụng đang phát triển chóng mặt, bao gồm các ứng dụng giải trí (nghe nhạc, xem phim, game, ...), ứng dụng đọc tin tức (báo điện tử, blog, ...) và còn nhiều loại ứng dụng khác nữa :P

Mỗi ứng dụng như tôi kể phía trên đều có cơ chế quản lý người dùng riêng. Thử tưởng tượng bạn cực kỳ yêu thích khoảng 10-20 ứng dụng, và việc ngồi đăng ký tài khoảng cho 10-20 ứng dụng đó thực sự là một "thảm họa" nếu như mỗi ứng dụng bạn lại thích một usernamepassword khác nhau (ý tôi là có thể hôm này bạn nghĩ ra username này hôm khác bạn thích username khác) hay là bạn phải lặp đi lặp lại công việc đăng ký usernamepassword, rồi active qua email tới tận 10-20 lần (yaoming)

Và nếu bạn là một tín đồ xem phim chẳng hạn, khi tham gia ứng dụng hoặc diễn đàn nào đó chắc hẳn các bạn đã gặp tình huống "Vui lòng đăng ký/đăng nhập thể xem link ẩn" =))

Bạn sẽ giải quyết thế nào trong tình huống tôi vừa đặt ra? Tôi tin là nhiều bạn sẽ có ngay câu trả lời là thôi mất chút thời gian đăng ký cũng được miễn là giải quyết được việc. Nhưng thật may mắn là chúng ta đã được cung cấp một giải pháp tuyệt vời hơn thế rất nhiều.

Nhận ra điều bất cập này, các "ông lớn" như Twitter, Facebook hay Google đã ngồi lại đàm phán với nhau và đưa ra một chuẩn mới có tên là Open Authentication. Trong nội dung của bài viết này, tôi sẽ giới thiệu với các bạn về chuẩn OAuth là gì? Cách thức hoạt động thế nào? Điểm mạnh và điểm yếu ra sao? Đồng thời phạm vi giới thiệu của tôi sẽ tập trung nhiều hơn vào chuẩn OAuth mới nhất hiện nay đó là Oauth2.

Giới thiệu

OAuth là gì?

OAuth là một phương thức chứng thực giúp các ứng dụng có thể chia sẻ tài nguyên với nhau mà không cần chia sẻ thông tin usernamepassword. Từ Auth ở đây mang 2 nghĩa:

  • Authentication: xác thực người dùng thông qua việc đăng nhập.
  • Authorization: cấp quyền truy cập vào các Resource.

Quay lại ví dụ ở phần mở đầu một chút, bạn có thể hiểu đơn giản là chúng ta đã đăng ký và có một tài khoản Facebook, và chúng ta sẽ dùng tài khoản này để đăng nhập ở 10-20 ứng dụng yêu thích của chúng ta. (honho)

Tuy nhiên trường hợp không mong muốn xảy ra đó là một trong số 20 ứng dụng của chúng ta bị hack và hacker lấy được các thông tin người dùng của ứng dụng. :-ss Trong trường hợp này bạn cũng không phải lo lắng quá về việc sẽ mất tài khoản (username + password) Facebook vì ứng dụng bị mất của bạn chỉ được Facebook chia sẻ cho một chìa khóa (token) chứa quyền hạn nhất định và không được phép truy cập vào thông tin username cũng như password của bạn.

Lịch sử

Chúng ta sẽ đi qua một vài điểm nhấn về lịch sử hình thành của OAuth (rất ngắn gọn thôi):

  • Năm 2006, Twitter đưa ra chuẩn OAuth đầu tiên có tên là OpenID, điểm yếu đó là yêu cầu người dùng phải cung cấp thông tin cá nhân (username + password).
  • Năm 2010, phát hành phiên bản chính thức đầu tiên của Oauth 1.0 (RFC 5849).
  • Sau đó lỗi bảo mật nghiêm trọng được phát hiện với tên gọi Session Fixation cho phép Hacker chiếm quyền truy cập vào tài nguyên của người dùng.
  • Năm 2012, OAuth2 ra đời, tuy vẫn còn những lỗi bảo mật như dùng Chrome để Hack Facebook nhưng hiện vẫn đang được sử dụng khá rộng rãi.

OAuth2 không đơn thuần chỉ là giao thức kết nối, nó là một "nền tảng" mà chúng ta phải triển khai ở cả hai phía: ClientServer. Sau đây hãy cùng làm quen với các tác nhân (hay đối tượng) tham gia vào hoạt động của OAuth2.

Các tác nhân (đối tượng) trong Oauth2

OAuth2 làm việc với 4 đối tượng mang những vai trò riêng:

  • Resource Owner (User): Là những người dùng ủy quyền cho ứng dụng cho phép truy cập tài khoản của họ. Sau đó ứng dụng được phép truy cập vào những dữ liệu người dùng nhưng bị giới hạn bởi những phạm vi (scope) được cấp phép. (VD: chỉ đọc hay được quyền ghi dữ liệu) => chính là bạn.
  • Client (Application): Là những ứng dụng mong muốn truy cập vào dữ liệu người dùng. Trước khi được phép tương tác với dữ liệu thì ứng dụng này phải qua bước ủy quyền của User, và phải được kiểm tra xác nhận thông qua API. => Có thể hiểu là các ứng dụng sử dụng Facebook, Twitter, Google API chẳng hạn.
  • Resource Server (API): Nơi lưu trữ thông tin tài khoản của User và được bảo mật.
  • Authorization Server (API): àm nhiệm vụ kiểm tra thông tin user (VD: ID), sau đó cấp quyền truy cập cho Application thông qua việc phát sinh "access token".

Resource ServerAuthorization Server chính là điểm khác biệt cơ bản giữa OAuth2OAuth1 khi tách biệt được hai thao tác: chứng thực (Authorization) và cung cấp thông tin người dùng (Resource) thành 2 Server.

Ta đã biết về 4 đối tượng trong mô hình OAuth2, vậy chúng hoạt động và tương tác với nhau như thế nào? Hãy cùng tìm hiểu ở mục tiếp theo.

Oauth2 hoạt động như thế nào?

Sơ đồ luồng hoạt động của OAuth2:
abstract_flow.png

  1. Application yêu cầu ủy quyền để truy cập vào Resource Server thông qua User
  2. Nếu User ủy quyền cho yêu cầu trên, Application sẽ nhận được giấy ủy quyền từ phía User (dưới dạng một token string nào đó chẳng hạn)
  3. Application gửi thông tin định danh (ID) của mình kèm theo giấy ủy quyền của User tới Authorization Server
  4. Nếu thông tin định danh được xác thực và giấy ủy quyền hợp lệ, Authorization Server sẽ trả về cho Application access_token. Đến đây quá trình ủy quyền hoàn tất.
  5. Để truy cập vào tài nguyên (resource) từ Resource Server và lấy thông tin, Application sẽ phải đưa ra access_token để xác thực.
  6. Nếu access_token hợp lệ, Resource Server sẽ trả về dữ liệu của tài nguyên đã được yêu cầu cho Application.

Luồng hoạt động thực tế có thể sẽ khác nhau tùy thuộc vào việc ứng dụng sử dụng loại ủy quyền (authorization grant type) nào, trên đây chỉ là ý tưởng chung để thực hiện.

Trong phần tiếp theo của bài viết này, tôi sẽ không đi sâu vào vấn đề làm thế nào để bạn có thể triển khai hệ thống OAuth trên Server của bạn mà chỉ giới thiệu thêm về luồng hoạt động của OAuth cũng như những việc cần làm khi ứng dụng của bạn phải triển khai chức năng giả sử như đăng nhập thông qua các dịch vụ như Facebook, Google, Github, ... và tôi sẽ lấy Github làm ví dụ để giải thích.

Đăng ký thông tin cho ứng dụng

Trước khi sử dụng Oauth cho ứng dụng, bạn phải đăng ký ứng dụng với bên cung cấp dịch vụ, một số thông tin cơ bản cần đăng ký như sau:

  • Tên ứng dụng (Application Name)
  • Website của ứng dụng (Application Website)
  • Redirect URI hoặc Callback URL: chính là địa chỉ sẽ quay về sau khi quá trình ủy quyền hoàn tất (cho phép hoặc từ chối từ phía User), chính vì thế mà địa chỉ quay về đó chính là nơi bạn sẽ phải thực hiện xử lý cho authorization codes hoặc access tokens.

Client ID và Client Secret là gì?

Khi ứng dụng của bạn được đăng ký, bên dịch vụ sẽ phát hành "thông tin chứng thực client" (client credentials) cho bạn bao gồm thông tin:

  • Client Identifier: là một chuỗi ký tự được sử dụng bởi Service API để định danh ứng dụng, đồng thời cũng được dùng để xây dựng "authorization URL" hiển thị phía User.
  • Client Secret: là một chuỗi ký tự được sử dụng để xác thực định danh (ID) của ứng dụng khi ứng dụng yêu cầu truy cập thông tin tài khoản của User. Chuỗi này được giữ bí mật giữa ApplicationAPI.

Ta sẽ có các thông tin quản lý ứng dụng dạng như sau:
github_register_application.png

Authorization Grant

Như luồng hoạt động đã mô tả ở trên thì 4 bước đầu tiên làm nhiệm vụ chính là lấy ủy quyền (authorization grant) và access token. Loại ủy quyền phụ thuộc vào phương thức mà Application sử dụng để yêu cầu ủy quyền, Oauth2 định nghĩa ra 4 loại:

  • Authorization Code: sử dụng với các server-side Application.
  • Implicit: sử dụng với các Mobile App (ứng dụng chạy trên thiết bị của User) hoặc Web App (có thể hiểu là Browser App cũng được, VD: Chrome Extension).
  • Resource Owner Password Credentials: sử dụng với các Trusted Application, kiểu như những ứng dụng của chính Service.
  • Client Credentials: sử dụng với các ứng dụng truy cập thông qua API.

Sau đây sẽ là chi tiết hơn về grant type và các tình huống cũng như các flow:

Grant Type: Authorization Code

Đây là một hình thức ủy quyền được dùng phổ biến nhất hiện nay, sơ đồ hoạt động như sau:
auth_code_flow.png

1. Yêu cầu authorization_code

Đầu tiên Application sẽ gửi User link đến nơi để bắt đầu quá trình nhận authorization_code. Ví dụ:

https://OAUTH_SERVER.DOMAIN/oauth/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=read

Trong đó:

  • OAUTH_SERVER.DOMAIN/login/oauth/authorize: API Authorization Endpoint.
  • client_id=CLIENT_ID: Client ID của Application sau khi đăng ký với Service.
  • redirect_uri=CALLBACK_URL: Nơi quay về sau khi sinh ra authorization_code.
  • response_type=code: tham số để hiểu được ứng dụng đang yêu cầu nhận authorization_code.
  • scope=read: định nghĩa phạm vi cho phép Application truy cập.

2. User ủy quyền cho Application

Khi User click vào link, việc đầu tiên cần làm là User đăng nhập để định danh người dùng. Sau đó User sẽ được dẫn đến màn hình để Cho phép hoặc Từ chối Application truy cập các thông tin của mình. Dưới đây là một ví dụ cụ thể về màn hình này:
authorize.png

3. Application nhận authorization_code

Nếu User click vào Authorize application, bên cung cấp dịch vụ sẽ chuyển hướng người dùng đến Application redirect URI (đã được đăng ký từ trước đó), tại đây Application sẽ phải thực hiện thao tác lưu lại authorization_code. Ví dụ:

https://APPLICATION.DOMAIN/callback?code=AUTHORIZATION_CODE

4. Application yêu cầu access_token

Sau khi nhận được authorization_code, Application cần thực hiện Request lên Oauth Server một lần nữa để lấy được access_token. Việc này phải được Application đảm nhận và User không cần thao tác thêm gì nữa. Link request có định dạng như sau:

https://OAUTH_SERVER.DOMAIN/oauth/token?client_id=CLIENT_ID&client_secret=CLIENT_SECRET&grant_type=authorization_code&code=AUTHORIZATION_CODE&redirect_uri=CALLBACK_URL

5. Application nhận access_token

Nếu quá trình ủy quyền (authorization) thành công, phía API sẽ gửi thông tin trả lời dưới dạng JSON chứa access_token (hoặc có thể kèm theo cả refesh_token) tới Application. Dữ liệu trả về có dạng như sau:

{
    "access_token":"ACCESS_TOKEN",
     "token_type":"bearer",
     "expires_in":2592000,
     "refresh_token":"REFRESH_TOKEN",
     "scope":"read",
     "uid":966996,
     "info":
        {
        "name":"Tungshooter",
        "email":"tungshooter@socool.com"
        }
     }

Tới đây Application đã được User ủy quyền truy cập. Application có thể sử dụng access_token để truy cập những tài nguyên của dịch vụ mà User cho phép, ví dụ thông tin email, ảnh avatar, ... cho tới khi access_token hết hạn sử dụng. Nếu phía API hỗ trợ và gửi về thêm cả thông tin refresh_token thì Application có thể sử dụng để đổi lấy access_token mới khi access_token cũ hết hạn.

Grant Type: Implicit

Loại ủy quyền này thường được sử dụng cho các ứng dụng di động hay cái ứng dụng chạy trên trình duyệt (VD: Chrome Extension). Được coi như một redirection-based flow, trong đó access_token được đưa đến Application thông qua trình duyệt (Browser). Bởi vậy phương thức ủy quyền này không thực hiện xác thực ID của Application mà tin tưởng hoàn toàn vào redirect URI (được đăng ký với Service).

Loại ủy quyền này không hỗ trợ refresh_token.

Ta có thể hiểu đơn giản luồng hoạt động như sau: User nhận được yêu cầu ủy quyền cho Application, sau đó Authorization Server truyền thẳng access_token tới Browser và sau đó truyền lại cho Application. Sơ đồ hoạt động như sau:
implicit_flow.png

1. Implicit Authorization Link

Cũng giống như phương thức ủy quyền trước đó, User sẽ được đưa tới một link để yêu cầu access_token từ API. Định dạng link như sau:

https://OAUTH_SERVER.DOMAIN/authorize?response_type=token&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=read

Cần lưu ý là tham số response_type ở đây là token.

2. User ủy quyền cho Application

Sau khi click vào link và thực hiện đăng nhập vào Service, User cũng được dẫn đến màn hình như phương thức ủy quyền sử dụng authorization_code, các thao tác tương tự nên tôi xin phép không giới thiệu lại nữa.

3. Browser nhận access_token thông qua Redirect URI

Sau khi User click Authorize applicaton, Service sẽ chuyển hướng Browser đến redirect URI (đã được đăng ký với Service). Trong URL có chứa thông tin access_token:

https://APPLICATION.DOMAIN/callback#token=ACCESS_TOKEN

Ví dụ trên mobile device thì Facebook SDK sẽ đảm nhận nhiệm vụ này.

4. Browser duy trì access_token

Sau khi Browser được chuyển hướng về redirect_uri, nhiệm vụ của Browser là duy trì access_token, ngoài ra tùy từng Service họ sẽ có thêm những tham số khác truyền về cùng access_token để điều hướng quay trở lại ứng dụng.

Bạn có thể hiểu đơn giản là trên thiết bị của chúng ta có nhiều ứng dụng (A, B, C, D, ...), giả sử ta đang ở ứng dụng A. Khi thực hiện quá trình ủy quyền bạn đã được điều hướng chuyển qua ứng dụng B (có thể tưởng tượng là ứng dụng trình duyệt Safari, Chrome,...). Khi kết thúc quá trình ủy quyền và nhận được access_token thì bạn vẫn đang ở ứng dụng B. Do vậy "những tham số khác" như tôi nói ở trên sẽ là những tham số mà sau đó ứng dụng B có thể biết được mình sẽ phải quay về ứng dụng A sau khi hoàn tất chứ không phải quay lại ứng dụng C, D, E, F, ...

5. Application trích xuất access_token

Tiếp tục câu chuyện kết thúc quá trình ủy quyền và dừng ở ứng dụng B (trình duyệt) với redirect_uri kèm theo các tham số khác. Tại URL trả về, nhiệm vụ ta phải thực hiện là phải sinh ra một đoạn script để trích xuất và lấy được thông tin access_token.

6. access_token được gửi đến Application

Sau khi trích xuất được thông tin access_token từ redirect_uri cùng thông tin của "những tham số khác", đoạn script như tôi nói ở bên trên còn phải thực hiện thêm nhiệm vụ điều hướng quay trở lại ứng dụng A kèm theo thông tin access_token tương ứng.

Đến đây quá trình ủy quyền ứng dụng hoàn tất và chúng ta có thể sử dụng access_token để truy cập và gọi API.

Chú ý: Do tôi chưa có dịp tiếp xúc và làm việc trực tiếp với các SDK của Facebook hay Google trên Mobile Application nên cũng chỉ hiểu được sơ qua luồng hoạt động (như mô tả bên trên), còn việc script thực hiện thế nào? làm những công việc gì? làm sao để biết có thông tin nào để quay lại ứng dụng vừa request? nếu các bạn có thông tin gì vui lòng chia sẻ thêm với tôi.

Grant Type: Resource Owner Password Credentials

Với loại ủy quyền này, User sẽ phải cung cấp thông tin usernamepassword trực tiếp cho Application sử dụng để lấy access_token. Cần lưu ý là loại ủy quyền này chỉ nên sử dụng cho những ứng dụng thực sự được tin tưởng (VD: những ứng dụng của chính Service hay những ứng dụng mặc định của hệ điều hành chẳng hạn).

Flow ở đây hết sức đơn giản, với một request ta có thể lấy luôn được access_token:

https://OAUTH_SERVER.DOMAIN/token?grant_type=password&username=USERNAME&password=PASSWORD&client_id=CLIENT_ID

Grant Type: Client Credentials

Loại ủy quyền này phục vụ cho việc truy cập vào chính thông tin tài khoản của Application tại Service. Có thể hiểu nôm na là Application mong muốn thay đổi thông tin description hoặc redirect_uri hay lấy thông tin của chính Application thông qua API. Bạn có thể thấy là loại ủy quyền này không có sự tham gia của *User.

Flow cũng đơn giản, Application cần gửi client_id, client_secret là có thể thực hiện tự ủy quyền cho chính mình:

https://OAUTH_SERVER.DOMAIN/token?grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET

Sử dụng Access Token

Sau khi đã có thông tin access_token, đọc document xem những API được cung cấp là ta có thể thực hiện một lời gọi API đơn giản với curl như sau:

curl -X POST -H "Authorization: Bearer ACCESS_TOKEN""https://API_SERVER.DOMAIN/v2/$OBJECT"

Refresh Token Flow

Sau khi access_token hết hạn, nếu bạn sử dụng để gọi API sẽ gặp thông báo lỗi "Invalid Token Error". Đừng lo vì nếu Service hỗ trợ cơ chế Refresh Token, chúng ta có thể dùng refresh_token để "đổi" access_token mới mà không cần thông qua User nữa. Ví dụ về request đổi access_token:

https://OAUTH_SERVER.DOMAIN/oauth/token?grant_type=refresh_token&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&refresh_token=REFRESH_TOKEN

Kết luận

  • Qua bài viết này tôi đã giới thiệu với các bạn về OAuth2: khái niệm, các đối tượng tham gia, các luồng hoạt động. Về cơ bản là nặng tính lý thuyết, còn việc triển khai chắc chắn sẽ còn gặp phải nhiều vấn đề nữa mà chúng ta phải xử lý.
  • Với các dịch vụ đã cung cấp sẵn OAuth như kiểu Facebook, Twitter, Google, Github, ... họ đã xây dựng sẵn phần Server và cả những thư viện cũng như bộ SDK cho các nền tảng rồi. Ta chỉ việc lấy bộ thư viện (hoặc SDK) về tích hợp và sử dụng.
  • Trong tình huống các bạn gặp yêu cầu phải tự xây dựng OAuth thì lý thuyết đã có ở trên rồi nhưng mà để thực hiện thì bạn phải tự code (yaoming). Ý kiến cá nhân tôi thì ngại nhất là phần Client thôi, còn không bạn có thể chỉ cần tự code phần Server và đưa ra các API để Client có thể triển khai theo đúng Flow mà bạn mong muốn. (I think it's not simple 😢).

Tham khảo

  1. https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2
  2. http://oauth.net/2/
  3. http://stackoverflow.com/questions/4113934/how-is-oauth-2-different-from-oauth-1
  4. https://aaronparecki.com/articles/2012/07/29/1/oauth2-simplified