Bu kılavuzda, Google'ın OAuth platformuyla OAuth 2.0 entegrasyonlarınızda DPoP (Demonstrating Proof-of-Possession)'un nasıl uygulanacağı ayrıntılı olarak açıklanmaktadır. DPoP (RFC 9449'da tanımlanmıştır), jetonları istemci tarafından oluşturulan asimetrik bir anahtar çiftine kriptografik olarak bağlayarak uygulamalarınızı jeton hırsızlığına ve yeniden oynatma saldırılarına karşı korur.
Yetkilendirme kodu akışı değişiklikleri
Mevcut bir OAuth 2.0 yetkilendirme kodu akışına DPoP eklemek için bir anahtar çifti oluşturulup depolanması, bir DPoP kanıtı JWT'si oluşturulması ve yetkilendirme kodu yenileme jetonuyla değiştirilirken kanıtın HTTP üstbilgisi olarak eklenmesi gerekir (Şekil 1'deki 5. ve 6. adımlarda gösterildiği gibi).
Yetkilendirme kodu isteği
Yetkilendirme isteği normal şekilde oluşturulur. Örneğin:
$ 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"
Yönlendirme URI parametresi olarak döndürülen yetkilendirme kodu, DPoP kanıtı oluşturulurken kullanılır. Yenileme jetonu, jeton uç noktasına yapılan tüm sonraki isteklerde HTTP üstbilgisi olarak eklenen kanıta bağlıdır.
Saf, sır içermeyen istemci tarafı SPA'lar, client_secret koşulu ve DPoP-Nonce başlığındaki CORS sınırlamaları nedeniyle DPoP'u doğrudan kullanamaz. SPA'ları güvenli hale getirmek için trafiği, gizli istemci olarak işlev gören, access_type=offline'ı etkinleştiren ve yenileme jetonunu sunucu tarafında bağlamak için DPoP'u kullanan bir Backend-for-Frontend (BFF) üzerinden yönlendirin.
DPoP kanıtını oluşturma
Bir kanıt, JOSE başlığı ve yük içerir.
Başlığı oluşturmak için bir EC P-256 (ES256) anahtar çifti oluşturun ve ortak anahtar koordinatlarını (x ve y) jwk parametresine ekleyin. RSA anahtar çifti de mümkündür ancak daha yüksek hesaplama maliyetleri nedeniyle önerilmez.
Bu, bir JOSE üstbilgisi örneğidir:
{
"typ": "dpop+jwt",
"alg": "ES256",
"jwk": {
"kty": "EC",
"crv": "P-256",
"x": "VC91y9ZYdfSWaDv8JaI6gx5ifOw2rn3YdqkAB51Uu6E",
"y": "ikPjOtea4k7fWPVrRYwaA4Ww6iVY3pOOICotHwwGV3o"
}
}
Kanıt yükünü oluşturmak için dört değer gerekir.
İki talep: htm: POST ve htu: https://oauth2.googleapis.com/token sabit değerlerdir ve Google'ın jeton uç noktasına istekte bulunurken değişmez.
Diğer iki talep: iat ve jti her istek için oluşturulmalıdır. iat değeri, yayınlanma zaman damgasıdır ve isteğe göre değişir. JWT kimliği (jti) talebinin değeri, değişim türüne bağlıdır. Yetkilendirme kodu, erişim ve yenileme jetonlarıyla değiştirildiğinde jti değeri, yetkilendirme kodunun Base64 ve URL olarak kodlanmış SHA256 karmasıdır (ör. jti =
BASE64URL(SHA-256(authorization_code))).
Bu, örnek bir yük gövdesidir:
{
"jti": "o29CN8LIY0l_N8iy5-ilon1guad9NFQHFOdXTzrBNck",
"htm": "POST",
"htu": "https://oauth2.googleapis.com/token",
"iat": 1784822025
}
JOSE başlığı ve yük gövdesi, jeton isteğindeki DPoP HTTP başlığında doğrudan kullanılmak üzere JWT (RFC7519) olarak kodlanır:
$ 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"
DPoP bağlı yenileme jetonu, DPoP-Nonce HTTP üst bilgisiyle birlikte döndürülür. Örneğin:
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"
}
Google'ın yetkilendirme sunucusu tarafından oluşturulan nonce, sonraki her jeton isteğine dahil edilmelidir. Nonce değerinin yalnızca bir kez kullanıldığını ve eksik, geçersiz, süresi dolmuş veya yeniden kullanılan nonce değerinin HTTP 400 yanıtıyla reddedildiğini unutmayın. Bu durumda, yeniden denemelerde kullanılmak üzere yeni bir nonce döndürülür.
Jeton yenileme akışında yapılan değişiklikler
Mevcut bir OAuth 2.0 jeton yenileme akışının güncellenmesi, yenileme jetonu yeni jetonlarla değiştirilirken HTTP üstbilgisi olarak bir DPoP kanıtının oluşturulup gönderilmesini gerektirir (Şekil 2'deki 2-5. adımlarda gösterildiği gibi).
DPoP kanıtını oluşturma
Jeton yenileme için kanıt oluşturma yöntemi, yetkilendirme kodu senaryosundan farklıdır. JOSE başlığı, yetkilendirme kodu isteği oluşturulurken daha önce açıklanan şekilde oluşturulur. Kanıt gövdesi benzer şekilde oluşturulur ancak nonce talebini içerir ve jti benzersiz bir rastgele dize içerir.
Yük gövdesini oluşturmak için daha önce döndürülen DPoP-Nonce HTTP başlık değeri, nonce talebine dahil edilmeli ve her istek için verilen zaman damgası (iat) güncellenmelidir. JWT kimliği (jti), yerleşik WebCrypto API'si crypto.getRandomValues(new Uint8Array(24)) kullanılarak istek başına oluşturulan ve Base64URL kodlamasıyla kodlanan benzersiz bir rastgele dizedir.
Bu, jti, nonce ve iat içeren örnek bir yük gövdesidir:
{
"jti": "o29CN8ZIY0l_K8iy5-ilon1gwad9NF6HFOdXTzrBNck",
"htm": "POST",
"htu": "https://oauth2.googleapis.com/token",
"nonce": "AN3XwJjZsjnb0ZuWkRlek8QU7wY-Zhf-5IP6tO0tORz0KgtDT1Bo8FX-w4nz3r5lnepI",
"iat": 1784822025
}
JOSE başlığı ve yük gövdesi, jeton isteğindeki DPoP HTTP başlığında doğrudan kullanılmak üzere JWT (RFC7519) olarak kodlanır.
Kanıt, jeton yenileme isteğine DPoP başlığı olarak eklenir:
$ 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"
Geçerliliği sona ermiş, yanlış veya yeniden kullanılan bir nonce kullanıldığında ya da farklı OAuth iş akışları arasında geçiş yapıldığında (ör. ilk yetkilendirme kodu değişiminden jeton yenileme isteğine geçiş) Google'ın sunucusu iş akışı izolasyonunu zorunlu kılar. Bu durumda sunucu, yeni iş akışı için yeni bir nonce ad alanı oluşturmak üzere nonce'u koşulsuz olarak bir HTTP 400 use_dpop_nonce sorgusuyla reddeder.
Bu, yeniden deneme ve DPoP-Nonce değeri kullanılarak yeni bir kanıt oluşturulmasını gerektiren 400 yanıtına bir örnektir:
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."
}
Başarı durumunda yeni bir nonce ve kısa ömürlü erişim jetonu döndürülür:
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"
}
Bir sonraki istekte kullanmak üzere DPoP-Nonce değerini kaydedin.
Ek ayrıntılar ve öneriler için Web Sunucusu Uygulamaları için OAuth 2.0'ı Kullanma ve En İyi Uygulamalar başlıklı makalelere göz atın.