Hướng dẫn sử dụng DPoP

Hướng dẫn này trình bày chi tiết cách triển khai DPoP (Chứng minh quyền sở hữu) trong các hoạt động tích hợp OAuth 2.0 với Nền tảng OAuth của Google. DPoP (được xác định trong RFC 9449) bảo vệ các ứng dụng của bạn khỏi hành vi đánh cắp mã thông báo và các cuộc tấn công phát lại bằng cách liên kết mã thông báo với một cặp khoá bất đối xứng do ứng dụng tạo theo cách mã hoá.

Thay đổi về quy trình sử dụng mã uỷ quyền

Để thêm DPoP vào quy trình Mã uỷ quyền OAuth 2.0 hiện có, bạn cần tạo và lưu trữ một cặp khoá, tạo JWT bằng chứng DPoP và đưa bằng chứng đó vào tiêu đề HTTP khi mã uỷ quyền được trao đổi để lấy mã làm mới như minh hoạ trong các bước 5 và 6 của Hình 1.

Quy trình sử dụng mã uỷ quyền có DPoP
Hình 1. Trình tự các sự kiện trong quy trình sử dụng mã uỷ quyền bằng DPoP.

Yêu cầu mã uỷ quyền

Yêu cầu uỷ quyền được tạo như bình thường. Ví dụ:

$ curl -G "https://accounts.google.com/o/oauth2/v2/auth" \
  --data-urlencode "client_id=YOUR_CLIENT_ID.apps.googleusercontent.com" \
  --data-urlencode "redirect_uri=http://127.0.0.1:8080" \
  --data-urlencode "response_type=code" \
  --data-urlencode "scope=calendar.readonly" \
  --data-urlencode "state=AI1Bvapj7E5SDmtW4gohcA" \
  --data-urlencode "code_challenge=PO4pPROl-31Wy9fVZ7uTW9Ga6CrjrSKsf4AAtx_JNM8" \
  --data-urlencode "code_challenge_method=S256" \
  --data-urlencode "nonce=PrMfmSNAvJFPQ7GnlEKUaw" \
  --data-urlencode "access_type=offline" \
  --data-urlencode "prompt=consent"

Mã uỷ quyền được trả về dưới dạng một tham số URI chuyển hướng được dùng trong quá trình tạo bằng chứng DPoP. Mã làm mới được liên kết với bằng chứng có trong tiêu đề HTTP trong tất cả các yêu cầu tiếp theo đến điểm cuối mã thông báo.

Các SPA thuần tuý, không có bí mật phía máy khách không thể sử dụng DPoP trực tiếp do yêu cầu client_secret và các hạn chế CORS đối với tiêu đề DPoP-Nonce. Để bảo mật SPA, hãy định tuyến lưu lượng truy cập thông qua một Backend-for-Frontend (BFF) đóng vai trò là một Ứng dụng khách bí mật, cho phép access_type=offline và sử dụng DPoP để liên kết mã làm mới phía máy chủ.

Tạo bằng chứng DPoP

Chứng minh thư chứa tiêu đề JOSE và phần dữ liệu thực tế.

Để tạo tiêu đề, hãy tạo một cặp khoá EC P-256 (ES256) và thêm toạ độ khoá công khai (xy) vào tham số jwk. Bạn cũng có thể dùng cặp khoá RSA nhưng không nên dùng vì chi phí tính toán cao hơn.

Sau đây là một ví dụ về tiêu đề JOSE:

{
  "typ": "dpop+jwt",
  "alg": "ES256",
  "jwk": {
    "kty": "EC",
    "crv": "P-256",
    "x": "VC91y9ZYdfSWaDv8JaI6gx5ifOw2rn3YdqkAB51Uu6E",
    "y": "ikPjOtea4k7fWPVrRYwaA4Ww6iVY3pOOICotHwwGV3o"
  }
}

Để tạo tải trọng bằng chứng, bạn cần có 4 giá trị.

Hai giá trị nhận dạng: htm: POSThtu: https://oauth2.googleapis.com/token là các giá trị cố định và không thay đổi khi bạn đưa ra yêu cầu đến điểm cuối mã thông báo của Google.

Hai thông báo xác nhận quyền sở hữu còn lại: iatjti phải được tạo cho mọi yêu cầu. Giá trị của iat là dấu thời gian được phát hành và thay đổi theo từng yêu cầu. Giá trị của khai báo JWT ID (jti) phụ thuộc vào loại hình trao đổi. Khi mã uỷ quyền được trao đổi để lấy mã truy cập và mã làm mới, giá trị của jti là hàm băm SHA256 được mã hoá theo chuẩn Base-64 và URL của mã uỷ quyền, chẳng hạn như jti = BASE64URL(SHA-256(authorization_code)).

Sau đây là một ví dụ về nội dung tải:

{
  "jti": "o29CN8LIY0l_N8iy5-ilon1guad9NFQHFOdXTzrBNck",
  "htm": "POST",
  "htu": "https://oauth2.googleapis.com/token",
  "iat": 1784822025
}

Tiêu đề JOSE và nội dung tải trọng được mã hoá dưới dạng JWT (RFC7519) để sử dụng trực tiếp trong tiêu đề HTTP DPoP trong yêu cầu mã thông báo:

$ curl -X POST https://oauth2.googleapis.com/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -H "DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6\
       IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoiVkM5MXk5WllkZlNXYUR2OEphSTZneDVpZ\
       k93MnJuM1lkcWtBQjUxVXU2RSIsInkiOiJpa1BqT3RlYTRrN2ZXUFZyUll3YUE0V3\
       c2aVZZM3BPT0lDb3RId3dHVjNvIn19.eyJqdGkiOiJvMjlDTjhMSVkwbF9OOGl5NS\
       1pbG9uMWd1YWQ5TkZRSEZPZFhUenJCTmNrIiwiaHRtIjoiUE9TVCIsImh0dSI6Imh\
       0dHBzOi8vb2F1dGgyLmdvb2dsZWFwaXMuY29tL3Rva2VuIiwiaWF0IjoxNzg0ODIy\
       MDI1fQ.OSdQCmqTng_uZmGK5UXf8hcEMtoOu7ucmYtl5mx4901RXnj6fJRJQmIeTq\
       fhprRBTG_RSJv2fPcWDqvQbDW7YA" \
  --data-urlencode "grant_type=authorization_code" \
  --data-urlencode "code=4/0AXEQxIDNpLD-qpSIvjHb2Hl10uS_2sk2GBRpO8UJQ78YZF3hZ9LB9kTA1xYLD4xisi4C5w" \
  --data-urlencode "redirect_uri=http://127.0.0.1:8080" \
  --data-urlencode "client_id=YOUR_CLIENT_ID.apps.googleusercontent.com" \
  --data-urlencode "client_secret=YOUR_CLIENT_SECRET" \
  --data-urlencode "code_verifier=q8ZztyVv7HH8E2M-SEL8WaB-7CPs68rejN5UZ9OdYgo"

Mã làm mới được liên kết với DPoP sẽ được trả về cùng với tiêu đề HTTP DPoP-Nonce, ví dụ:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
DPoP-Nonce: AN3XwJjZsjnb0ZuWkRlek8QU7wY-Zhf-5IP6tO0tORz0KgtDT1Bo8FX-w4nz3r5lnepI

{
  "access_token": "ya29.a0ARGnu0aebRL97B91dmvm14gTug5wpItFf9MVWq12Hja6yv09A_qxa4T73_z2gFbf32qR4RXispQ7vnOzv6gn0APLQrF51LVa6AOqCVPH2Tupocv8y0JHu4ByEbvgXEEhiHEU8Xa9_w3i-PKBPsKWiLi210RCZdqJjLXkcRrGnoPPjbGPzOPtm6KCJjPrNHG16caOWecaCgYKASESARASFQHGX2MiBn7ihbbk_n-buCbOfl2TDA0206",
  "expires_in": 3599,
  "refresh_token": "1//06dUPZ9FIBQm3CgYIARAAGAYSNwF-L9IrJwuIEKUA_zbBPU-xoCDGM0QrDu7-jv7cMQZ0kARPUK9WhwfFFfbOVEgXDQKmFh4w9GM",
  "scope": "https://www.googleapis.com/auth/calendar.readonly",
  "token_type": "Bearer"
}

Số chỉ dùng một lần do máy chủ uỷ quyền của Google tạo phải có trong mọi yêu cầu mã thông báo tiếp theo. Xin lưu ý rằng giá trị số chỉ dùng một lần và giá trị số bị thiếu, không hợp lệ, hết hạn hoặc được dùng lại sẽ bị từ chối bằng phản hồi HTTP 400. Trong trường hợp này, một số chỉ dùng một lần mới sẽ được trả về để sử dụng trong các lần thử lại.

Các thay đổi đối với quy trình làm mới mã thông báo

Để cập nhật quy trình Làm mới mã thông báo OAuth 2.0 hiện có, bạn cần tạo và gửi bằng chứng DPoP dưới dạng tiêu đề HTTP khi trao đổi mã làm mới để lấy mã thông báo mới như minh hoạ trong các bước 2-5 của Hình 2.

Quy trình làm mới mã thông báo bằng DPoP
Hình 2. Trình tự các sự kiện trong quy trình Làm mới mã thông báo có xử lý lỗi và thử lại.

Xây dựng bằng chứng DPoP

Phương thức tạo bằng chứng để làm mới mã thông báo khác với trường hợp mã uỷ quyền. Tiêu đề JOSE được tạo theo cách tương tự như cách được mô tả trước đó khi tạo yêu cầu mã uỷ quyền. Nội dung bằng chứng được tạo tương tự nhưng có một yêu cầu noncejti chứa một chuỗi ngẫu nhiên duy nhất.

Để tạo phần nội dung tải trọng, bạn phải thêm giá trị tiêu đề HTTP DPoP-Nonce đã trả về trước đó vào yêu cầu nonce và dấu thời gian được phát hành (iat) được cập nhật cho mọi yêu cầu. JWT ID (jti) là một chuỗi ngẫu nhiên duy nhất được tạo cho mỗi yêu cầu, bằng cách sử dụng API WebCrypto tích hợp crypto.getRandomValues(new Uint8Array(24)) và mã hoá chuỗi theo Base64URL.

Đây là một ví dụ về phần nội dung tải trọng chứa jti, nonceiat:

{
  "jti": "o29CN8ZIY0l_K8iy5-ilon1gwad9NF6HFOdXTzrBNck",
  "htm": "POST",
  "htu": "https://oauth2.googleapis.com/token",
  "nonce": "AN3XwJjZsjnb0ZuWkRlek8QU7wY-Zhf-5IP6tO0tORz0KgtDT1Bo8FX-w4nz3r5lnepI",
  "iat": 1784822025
}

Tiêu đề và nội dung tải của JOSE được mã hoá dưới dạng JWT (RFC7519) để sử dụng trực tiếp trong tiêu đề HTTP DPoP trong yêu cầu mã thông báo.

Bằng chứng được thêm dưới dạng tiêu đề DPoP vào yêu cầu làm mới mã thông báo:

$ curl -X POST https://oauth2.googleapis.com/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -H "DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6\
       IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoiVkM5MXk5WllkZlNXYUR2OEphSTZneDVpZ\
       k93MnJuM1lkcWtBQjUxVXU2RSIsInkiOiJpa1BqT3RlYTRrN2ZXUFZyUll3YUE0V3\
       c2aVZZM3BPT0lDb3RId3dHVjNvIn19.eyJqdGkiOiJvMjlDTjhaSVkwbF9LOGl5NS\
       1pbG9uMWd1YWQ5TkY2SEZPZFhUenJCTmNrIiwiaHRtIjoiUE9TVCIsImh0dSI6Imh\
       0dHBzOi8vb2F1dGgyLmdvb2dsZWFwaXMuY29tL3Rva2VuIiwibm9uY2UiOiJBTjNY\
       d0pqWnNqbmIwWnVXa1JsZWs4UVU3d1ktWmhmLTVJUDZ0TzB0T1J6MEtndERUMUJvO\
       EZYLXc0bnozcjVsbmVwSSIsImlhdCI6MTc4NDgyMjAyNX0.MEQCIDm09AXo2c9sov\
       GrTUkrbEB_k9mra_Dkji-CQ9mSZVP1AiBxbiqkCE7Dt9RKyUT_3kj7q1vCvVggwnW\
       JNX3P3vO1mw" \
  --data-urlencode "grant_type=refresh_token" \
  --data-urlencode "refresh_token=1//06dUPZ9FIBQm3CgYIARAAGAYSNwF-L9IrJwuIEKUA_zbBPU-xoCDGM0QrDu7-jv7cMQZ0kARPUK9WhwfFFfbOVEgXDQKmFh4w9GM" \
  --data-urlencode "client_id=YOUR_CLIENT_ID.apps.googleusercontent.com" \
  --data-urlencode "client_secret=YOUR_CLIENT_SECRET"

Khi một số chỉ dùng một lần đã hết hạn, không chính xác hoặc được dùng lại được sử dụng hoặc khi chuyển đổi giữa các quy trình OAuth khác nhau (chẳng hạn như chuyển từ quy trình Trao đổi mã uỷ quyền ban đầu sang yêu cầu Làm mới mã thông báo), máy chủ của Google sẽ thực thi quy trình cách ly. Điều này có nghĩa là máy chủ sẽ vô điều kiện từ chối số chỉ dùng một lần bằng một thử thách 400 use_dpop_nonce HTTP để thiết lập một không gian tên số chỉ dùng một lần mới cho quy trình mới.

Đây là ví dụ về phản hồi 400 yêu cầu thử lại và cần tạo bằng chứng mới bằng cách sử dụng giá trị DPoP-Nonce:

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
DPoP-Nonce: AO4t07Kf85RJXmltUhiAiELLPPrJ4zOi66zWxU1uDZbhRcahFBYvT0WlcjSSXULXknSA

{
  "error": "use_dpop_nonce",
  "error_description": "New DPoP nonce issued due to invalid or expired challenge."
}

Khi thành công, một số chỉ dùng một lần mới và mã truy cập có thời hạn ngắn sẽ được trả về:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
DPoP-Nonce: AO4t07IXuovyCbtLEr6VVFZQ_Kb78MMOXTt6-CyZpsJeF62HZ3P_EW55XbWqYcU76Jg=

{
  "access_token": "ya29.a0ARGnu0bDj9BAQYVbF5hi3vw-brBUZBZu1bnInk1hS7gueqEb6QPqUjDGb0MMj9A0QX5FRrJo3FDw-DEDtvVbRUdeCgjwsL_LVVFXz-p-MUyiFyRoufI4KC0Go9aq5cEjD_BWvOJLMSIY6_EnwnhqDgk0XxvzaaAxDnv8PXJAGev_UotcfApstqi0NCxbfi-6Kgull9QaCgYKAUQSARASFQHGX2MiZpMjRS6z4S0RjOkNxn2o1Q0206",
  "expires_in": 3599,
  "scope": "https://www.googleapis.com/auth/calendar.readonly",
  "token_type": "Bearer",
  "challenge": "AO4t07IXuovyCbtLEr6VVFZQ_Kb78MMOXTt6-CyZpsJeF62HZ3P_EW55XbWqYcU76Jg"
}

Lưu giá trị DPoP-Nonce để sử dụng trong yêu cầu tiếp theo.

Hãy xem bài viết Sử dụng OAuth 2.0 cho ứng dụng máy chủ webCác phương pháp hay nhất để biết thêm thông tin chi tiết và các đề xuất.