What is It?

OAuth là một giao thức giúp người dùng ủy quyền cho các ứng dụng bên thứ ba truy cập đến các tài nguyên được bảo vệ của người dùng nằm trên một dịch vụ khác mà không làm lộ thông tin tài khoản hoặc danh tính của người dùng. Cụ thể hơn, đây chính là cơ chế cho phép chúng ta đăng nhập bằng tài khoản Google, Facebook, … trên các ứng dụng mà không cần đăng ký tài khoản sử dụng username và password.

Roles

OAuth có các vai trò sau:

  • Resource owner: là người dùng ủy quyền cho ứng dụng truy cập đến tài khoản của họ. Quyền truy cập này bị giới hạn trong phạm vi cho phép của người dùng.
  • Client: là ứng dụng muốn truy cập đến tài khoản của người dùng. Trước khi có thể làm việc đó, nó cần được ủy quyền bởi người dùng và việc ủy quyền cần được xác thực thông qua API.
  • Resource server: là server host tài khoản được bảo vệ của người dùng.
  • Authorization server: là server xác thực danh tính của người dùng và cấp phát access token cho ứng dụng.

Abstract Protocol Flow

Cách các vai trò tương tác với nhau:

 +--------+                               +---------------+
 |        |--(1)- Authorization Request ->|   Resource    |
 |        |                               |     Owner     |
 |        |<-(2)-- Authorization Grant ---|               |
 |        |                               +---------------+
 |        |
 |        |                               +---------------+
 |        |--(3)-- Authorization Grant -->| Authorization |
 | Client |                               |     Server    |
 |        |<-(4)----- Access Token -------|               |
 |        |                               +---------------+
 |        |
 |        |                               +---------------+
 |        |--(5)----- Access Token ------>|    Resource   |
 |        |                               |     Server    |
 |        |<-(6)--- Protected Resource ---|               |
 +--------+                               +---------------+

Giải thích chi tiết các bước trong sơ đồ trên:

  1. Ứng dụng yêu cầu ủy quyền từ người dùng để truy cập đến tài nguyên.
  2. Nếu người dùng chấp thuận yêu cầu thì ứng dụng sẽ được cấp ủy quyền.
  3. Ứng dụng yêu cầu access token bằng cách xác thực với authorization server và trình bày ủy quyền đã được cấp.
  4. Nếu ứng dụng xác thực thành công và ủy quyền đã được cấp là hợp lệ thì authorization server sẽ cấp cho ứng dụng access token. Giai đoạn ủy quyền kết thúc.
  5. Ứng dụng yêu cầu resource từ resource server bằng cách sử dụng access token.
  6. Nếu access token là hợp lệ thì resource server sẽ trả về resource cho ứng dụng.

Luồng trên có thể khác đối với từng cách cấp ủy quyền.

Application Registration

Trước khi sử dụng OAuth trong ứng dụng, chúng ta cần đăng ký ứng dụng với dịch vụ OAuth. Khi đăng ký cần cung cấp những thông tin sau:

  • Tên ứng dụng.
  • Website của ứng dụng.
  • Redirect URI hoặc callback URL

Callback URL là địa chỉ mà dịch vụ OAuth sẽ chuyển hướng người dùng đến sau khi họ cấp ủy quyền (hoặc từ chối cấp ủy quyền) cho ứng dụng.

Client ID and Client Secret

Khi ứng dụng đã được đăng ký, dịch vụ OAuth sẽ cấp phát client credentials dưới dạng một client ID và một client secret.

  • Client ID là một chuỗi công khai giúp dịch vụ định danh ứng dụng và được sử dụng để xây dựng các authorization URL gửi cho người dùng.
  • Client secret giúp xác thực định danh của ứng dụng với dịch vụ OAuth khi ứng dụng yêu cầu quyền truy cập đến tài khoản của người dùng. Giá trị này cần giữ bí mật giữa ứng dụng và dịch vụ OAuth.

Authorization Grant Type

Một số kiểu cấp ủy quyền:

  • Authorization code: thường sử dụng cho các ứng dụng web hoặc di động.
  • Proof key for code exchange: là một phần mở rộng của authorization code.
  • Client credentials: sử dụng để ứng dụng truy cập vào tài khoản của chính nó.
  • Device code: sử dụng cho các thiết bị không có trình duyệt và bị giới hạn về input.

Authorization Code

Kiểu cấp ủy quyền này dựa trên sự chuyển hướng (redirection-based). Cụ thể hơn, nó cần phải được khởi tạo thông qua trình duyệt.

Get the User’s Permission

Đầu tiên, người dùng được cung cấp một URL có dạng như sau:

https://authorization-server.com/auth
 ?response_type=code
 &client_id=29352915982374239857
 &redirect_uri=https%3A%2F%2Fexample-app.com%2Fcallback
 &scope=create+delete
 &state=xcoiv98y2kd22vusuye3kch

Giải thích URL trên:

  • https://authorization-server.com/auth: URL của authorization server.
  • response_type: cho biết ứng dụng sử dụng authorization code grant type.
  • client_id: là client ID của ứng dụng.
  • redirect_uri: đường dẫn mà authorization server sẽ điều hướng user-agent sau khi authorization code được cấp phát.
  • scope: chỉ định các quyền hạn mà ứng dụng cần. Có giá trị là một hoặc nhiều chuỗi phân cách với nhau bởi khoảng trắng.
  • state: là một chuỗi ngẫu nhiên sinh ra bởi ứng dụng dùng để để chống CSRF attack. Giá trị này sẽ được kiểm tra với giá trị trả về bởi authorization server sau khi người dùng ủy quyền cho ứng dụng.

Khi người dùng click vào link, authorization server sẽ cho người dùng chọn giữa việc cho phép ủy quyền hoặc từ chối ủy quyền ứng dụng. Ví dụ:

Redirect Back to the Application

Nếu người dùng chấp thuận thì dịch vụ sẽ chuyển hướng user-agent đến redirect URI mà ứng dụng đã chỉ định trước đó kèm theo authorization code và state. Ví dụ, người dùng có thể được chuyển hướng đến URL sau đây:

https://example-app.com/callback
 ?code=g0ZGZmNjVmOWIjNTk2NTk4ZTYyZGI3
 &state=xcoiv98y2kd22vusuye3kch

Phân tích URL trên:

  • Giá trị code chính là authorization code được sinh ra bởi authorization server. Giá trị này thường có thời gian tồn tại ngắn (khoảng 1 đến 10 phút tùy vào dịch vụ OAuth).
  • Giá trị state sẽ được ứng dụng kiểm tra xem có khớp với giá trị trước đó mà nó đã gửi cho authorization server hay không nhằm ngăn chặn CSRF attack và các kiểu tấn công liên quan.

Exchange the Authorization Code for an Access Token

Sau khi có authorization code, ứng dụng sẽ yêu cầu access token bằng cách gửi authorization code kèm các thông tin xác thực của nó (bao gồm cả client secret) đến token endpoint của dịch vụ OAuth trong một POST request. Các tham số trong POST request bao gồm:

  • grant_type=authorization_code: cho biết ứng dụng sử dụng authorization code grant type.
  • code: authorization code.
  • redirect_uri: giống với redirect URI khi yêu cầu authorization code. Một số dịch vụ OAuth không yêu cầu trường này.
  • client_id: client ID của ứng dụng.
  • client_secret: client secret của ứng dụng.

Minh họa request:

POST /token HTTP/2
Host: authorization-server.com
 
grant_type=authorization_code
&client_id=29352915982374239857
&client_secret=tyo1bxKINxz5nSXQhhMaTkj7yzwpRlwBUkCL94uub3vN-rIT
&redirect_uri=https%3A%2F%2Fexample-app.com%2Fcallback
&code=g0ZGZmNjVmOWIjNTk2NTk4ZTYyZGI3

Token endpoint sẽ kiểm tra tất cả các tham số trên nhằm đảm bảo authorization code chưa bị hết hạn và client credentials là hợp lệ. Nếu hai điều kiện này thỏa mãn thì nó sẽ tạo ra access token và chèn vào trong response:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
 
{
  "access_token":"MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3",
  "token_type":"bearer",
  "expires_in":3600,
  "refresh_token":"IwOGYzYTlmM2YxOTQ5MGE3YmNmMDFkNTVk",
  "scope":"create delete"
}

Lúc này, ứng dụng chính thức được ủy quyền. Nếu access token hết hạn mà refresh token được đã cấp phát thì ứng dụng có thể sử dụng refresh token để yêu cầu access token mới.

Minh họa toàn bộ quy trình:

Proof Key for Code Exchange (PKCE)

Nếu ứng dụng sử dụng kiểu cấp ủy quyền là authorization code cho các ứng dụng di động hoặc các ứng dụng không thể lưu client secret thì có khả năng authorization code bị attacker đánh cắp. Authorization code có một phần mở rộng tên là Proof Key for Code Exchange (PKCE, phát âm là “pixie”) có thể giải quyết vấn đề này. Tổng quát hơn, PKCE được dùng để ngăn chặn CSRF attack và authorization code injection attack.

Với PKCE, ứng dụng cần tạo ra và lưu một secret key (hay còn gọi là code verifier). Code verifier có các tính chất sau:

  • Miền giá trị là a-Z0-9 cùng các ký tự -, ., _~.
  • Có độ dài từ 43 đến 128 ký tự.

Ứng dụng sau đó dùng code verifier để tạo ra code challenge. Đối với các thiết bị có thể thực hiện thuật toán băm mật mã SHA256 thì code challenge là SHA256 hash của code verifier dưới dạng base64 (base64url(sha256(code_verifier))). Trong trường hợp thiết bị không thực hiện được hàm băm SHA256 thì code challenge cũng chính là code verifier.

Sau khi có code challenge thì ứng dụng sẽ đính kèm nó cùng với phương pháp biến đổi (nếu có) trong một authorization request (là request yêu cầu authorization code). Ví dụ:

https://authorization-server.com/authorize?
  response_type=code
  &client_id=f9eFS7oUdhFlWRjBALohlgo5
  &redirect_uri=https://www.oauth.com/playground/authorization-code-with-pkce.html
  &scope=photo+offline_access
  &state=Hn1XDyE9iVWCOCFM
  &code_challenge=vLh+0M0Av8icTMS8YNi7pktfBwYluFyLAIa6+5vxsaY
  &code_challenge_method=S256

Nếu người dùng chấp thuận việc ủy quyền thì authorization serever sẽ lưu lại code challenge và phương pháp biến đổi rồi trả về authorization code cho ứng dụng.

Sau đó, ứng dụng sẽ gửi access token request kèm theo code verifier mà nó đã tạo trước đó. Request này có dạng như sau:

POST /token HTTP/2
Host: authorization-server.com
 
grant_type=authorization_code
&client_id=f9eFS7oUdhFlWRjBALohlgo5
&client_secret=tyo1bxKINxz5nSXQhhMaTkj7yzwpRlwBUkCL94uub3vN-rIT
&redirect_uri=https%3A%2F%2Fwww.oauth.com%2Fplayground%2Fauthorization-code-with-pkce.html
&code=1O4lCoWSwtsHTNsCkdeDh6ZiBra--5NRILI3j7vmNIuqEabR
&code_verifier=yZiHm-RLoqytVzlrwJ7kzlBinQn9LKjlw1aHls6X93qzUKQ1

Sau khi nhận được code verifier, authorization server biến đổi nó thành code challenge sử dụng phương pháp biến đổi mà đã được lưu trước đó. Nếu code challenge tạo thành từ code verifier không match với code challenge đã được lưu thì authorization server sẽ từ chối yêu cầu cấp phát access token. Ngược lại thì authorization server sẽ trả về response có chứa access token:

{
  "token_type": "Bearer",
  "expires_in": 86400,
  "access_token": "_FNyBNTLS7vVi25ZLTWx6pmmk3iBxDpGzc8T6Y0mLuH-qCDT4XKtnyLt6Tg0ezwtlnM7FvcW",
  "scope": "photo offline_access",
  "refresh_token": "tuoENrMmU_4FIqNxCPJQjY5p"
}

Minh họa quy trình:

Client Credentials

Kiểu cấp ủy quyền này cho phép ứng dụng truy cập vào tài khoản của chính nó thay vì tài khoản của người dùng.

Ứng dụng request access token bằng cách gửi credential của chính nó (bao gồm client ID và client secret) cho authorization server. Ví dụ:

POST /token HTTP/2
Host: authorization-server.com
 
grant_type=client_credentials
&client_id=CLIENT_ID
&client_secret=CLIENT_SECRET

Ngoài các tham số trên thì còn có tham số scope (tùy thuộc vào dịch vụ OAuth).

Nếu credential gửi lên là hợp lệ thì ứng dụng sẽ nhận được access token cho phép truy cập vào tài khoản của ứng dụng.

Device Code

Giúp các thiết bị không có trình duyệt và có input bị hạn chế có thể nhận access token và truy cập vào tài khoản của người dùng. Ví dụ, nếu người dùng muốn đăng nhập vào ứng dụng trên thiết bị mà không có bàn phím như smart TV hoặc game console thì có thể sử dụng kiểu cấp ủy quyền này.

Request a Device Code

Khi người dùng khởi động ứng dụng, nó sẽ gửi một POST request đến device authorization endpoint nhằm yêu cầu device code. Ví dụ:

POST /device HTTP/2
HosT: oauth.example.com
 
client_id=CLIENT_id

Tell the User to Enter the Code

Một số field quan trọng có trong response trả về của device authorization endpoint:

  • device_code: là một device code độc nhất nhằm định danh thiết bị. Device code có thể là QR code.
  • user_code: là một giá trị mà người dùng sẽ nhập trên một thiết bị dễ xác thực hơn chẳng hạn laptop hoặc điện thoại.
  • verification_uri: là đường dẫn mà người dùng cần truy cập để nhập user code nhằm chấp thuận hoặc từ chối ủy quyền cho ứng dụng.

Bên dưới là một response mẫu từ device authorization endpoint:

{
  "device_code": "IO2RUI3SAH0IQuESHAEBAeYOO8UPAI",
  "user_code": "RSIK-KRAM",
  "verification_uri": "https://example.okta.com/device",
  "interval": 10,
  "expires_in": 1600
}

Minh họa việc hiển thị trên thiết bị:

Poll the Token Endpoint

Trong thời gian người dùng truy cập verification URL, đăng nhập, nhập user code và chấp thuận yêu cầu ủy quyền, thiết bị sẽ liên tục thăm dò token endpoint đến khi nào nó nhận device code hoặc lỗi khác authorization_pending. Request thăm dò có dạng như sau:

POST /token HTTP/2
Host: example.okta.com
 
grant_type=urn:ietf:params:oauth:grant-type:device_code
&client_id=https%3A%2F%2Fwww.oauth.com%2Fplayground%2F
&device_code=NGU5OWFiNjQ5YmQwNGY3YTdmZTEyNzQ3YzQ1YSA

Response có chứa lỗi authorization_pending:

HTTP/1.1 400 Bad Request
 
{
  "error": "authorization_pending"
}

Lỗi này cho biết rằng người dùng chưa chấp thuận hoặc từ chối yêu cầu ủy quyền.

Một số lỗi khác:

  • slow_down: thiết bị thăm dò quá thường xuyên.
  • access_denied: người dùng từ chối yêu cầu ủy quyền.
  • expired_token: token hết hạn.

Nếu người dùng chấp thuận yêu cầu ủy quyền thì token endpoint sẽ trả về access token. Ví dụ:

HTTP/1.1 200 OK
 
{
  "token_type": "Bearer",
  "access_token": "RsT5OjbzRn430zqMLgV3Ia",
  "expires_in": 3600,
  "refresh_token": "b7a3fac6b10e13bb3a276c2aab35e97298a060e0ede5b43ed1f720a8"
}

Access Token Response

Successful Response

Nếu người dùng chấp nhận ủy quyền cho ứng dụng thì authorization server sẽ trả về access token kèm theo một vài thông tin về việc ủy quyền. Các field có trong response là:

  • access_token (bắt buộc): access token
  • token_type (bắt buộc): loại token, thường là Bearer.
  • expires_in (khuyến nghị): nếu token hết hạn, server nên phản hồi khoảng thời gian mà token được cấp.
  • refresh_token (tùy chọn): dùng để làm mới token.
  • scope (optional): nếu scope mà người dùng cấp cho ứng dụng khớp với scope mà ứng dụng yêu cầu thì có thể bỏ trường này. Ngược lại, nếu scope mà người dùng cấp khác với scope của ứng dụng (chẳng hạn như khi người dùng thay đổi scope) thì trường này là bắt buộc.

Khi trả về response có access token, server cũng cần phải thêm vào header Cache-Control: no-store nhằm đảm bảo ứng dụng không cache request này.

Ví dụ:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
 
{
  "access_token":"MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3",
  "token_type":"Bearer",
  "expires_in":3600,
  "refresh_token":"IwOGYzYTlmM2YxOTQ5MGE3YmNmMDFkNTVk",
  "scope":"create"
}

Unsuccessful Response

Nếu access token không hợp lệ thì authorization server có thể trả về error response. Các error response sẽ có HTTP status code với tham số error (bắt buộc), error_descriptionerror_uri.

Một số giá trị của error:

  • invalid_request: request bị thiếu mất tham số bắt buộc hoặc server không thể xử lý request.
  • invalid_client: không thể xác thực ứng dụng. Riêng lỗi này thì status code là 401.
  • invalid_grant: authorization code (hoặc mật khẩu của người dùng đối với password grant type) không hợp lệ hoặc hết hạn. Lỗi này cũng có thể xảy ra khi redirect URL trong quá trình authorization không khớp với redirect URL trong access token request.
  • invalid_scope: scope gửi lên không hợp lệ.
  • unauthorized_client: không hỗ trợ client.
  • unsupported_grant_type: không hỗ trợ grant type.

Ví dụ:

HTTP/1.1 400 Bad Request
Content-Type: application/json
Cache-Control: no-store
 
{
  "error": "invalid_request",
  "error_description": "Request was missing the 'redirect_uri' parameter.",
  "error_uri": "See the full API docs at https://authorization-server.com/docs/access_token"
}
 

Refresh Token

Khi access token hết hạn thì ứng dụng sẽ gửi request để refresh token. Các tham số có trong request:

  • grant_type=refresh_token: chỉ định rằng ứng dụng cần làm mới token.
  • refresh_token: refresh token đã được cấp phát cho ứng dụng.
  • client_idclient_secret: nếu ứng dụng được cấp client secret thì cần sử dụng hai tham số này. Một số dịch vụ còn chấp nhận việc đặt client ID và client secret ở trong HTTP header.

Ví dụ:

POST /oauth/token HTTP/1.1
Host: authorization-server.com
 
grant_type=refresh_token
&refresh_token=xxxxxxxxxxx
&client_id=xxxxxxxxxx
&client_secret=xxxxxxxxxx

Sau khi thực hiện việc xác thực cho ứng dụng (nếu ứng dụng được cấp client secret), authorization server sẽ kiểm tra xem refresh token có hợp lệ hay không. Nếu hợp lệ thì server sẽ trả về một access token và refresh token mới (có thể không có). Nếu trong response không có refresh token thì ứng dụng vẫn có thể dùng refresh token cũ để làm mới access token.

list
from outgoing([[Open Authorization]])
sort file.ctime asc

Resources