PureBasic
PureBasic
Implementing DPoP with OAuth2 Client Credentials
See more AI Examples
Demonstrating Proof-of-Possession (DPoP) is an OAuth 2.0 extension (RFC 9449) that secures access tokens by binding them to a specific client’s private key, preventing stolen token misuse. It requires the client to sign a JWT (DPoP Proof) with every request, proving they hold the private key.This example shows how to use Chilkat v11.4.0 or later to automatically add the following headers to HTTP requests:
Authorization: DPoP <access_token> DPoP: <new JWT signed with same key>
Chilkat PureBasic Downloads
IncludeFile "CkHttp.pb"
IncludeFile "CkJsonObject.pb"
IncludeFile "CkPrivateKey.pb"
Procedure ChilkatExample()
success.i = 0
privKey.i = CkPrivateKey::ckCreate()
If privKey.i = 0
Debug "Failed to create object."
ProcedureReturn
EndIf
; Load your ECDSA private key. (Keys uses with DPoP must always be ECDSA keys. Not RSA.)
success = CkPrivateKey::ckLoadAnyFormatFile(privKey,"qa_data/pem/ecc_privKey.pem","")
If success = 0
Debug CkPrivateKey::ckLastErrorText(privKey)
CkPrivateKey::ckDispose(privKey)
ProcedureReturn
EndIf
tokenEndpoint.s = "https://demo.duendesoftware.com/connect/token"
apiEndpoint.s = "https://demo.duendesoftware.com/api/dpop/test"
; Provide the details needed for Chilkat to automatically fetch the access token
; using the client_credentials OAuth2 flow.
json.i = CkJsonObject::ckCreate()
If json.i = 0
Debug "Failed to create object."
ProcedureReturn
EndIf
CkJsonObject::ckUpdateString(json,"client_secret","secret")
CkJsonObject::ckUpdateString(json,"client_id","m2m.dpop")
CkJsonObject::ckUpdateString(json,"token_endpoint",tokenEndpoint)
CkJsonObject::ckUpdateString(json,"scope","api")
http.i = CkHttp::ckCreate()
If http.i = 0
Debug "Failed to create object."
ProcedureReturn
EndIf
CkHttp::setCkSessionLogFilename(http, "c:/aaworkarea/sessionLog.txt")
; Provide private key to be used with DPoP
success = CkHttp::ckSetDPoPKey(http,privKey)
If success = 0
Debug CkHttp::ckLastErrorText(http)
CkPrivateKey::ckDispose(privKey)
CkJsonObject::ckDispose(json)
CkHttp::ckDispose(http)
ProcedureReturn
EndIf
CkHttp::setCkAuthToken(http, CkJsonObject::ckEmit(json))
; When we make the first request to an API endpoint, we don't yet have
; an OAuth2 access token. Chilkat (internally) will use the token endpoint
; to automatically get an access token.
; Chilkat will then add the following headers to the request sent to the API endpoint:
; Authorization: DPoP <access_token>
; DPoP: <new JWT signed with same key>
; For subsequent requests using the same Chilkat HTTP object instance,
; the same access token will be used with a newly generated DPoP header for each request.
; When the access_token is expired or about to expire, Chilkat will automatically (internally)
; refresh the access token. Your application does not need to be concerned with the
; complexities of fetching the access token or managing the refresh, or generating
; the DPoP header for each request.
resp.s = CkHttp::ckQuickGetStr(http,apiEndpoint)
If CkHttp::ckLastMethodSuccess(http) = 0
Debug CkHttp::ckLastErrorText(http)
CkPrivateKey::ckDispose(privKey)
CkJsonObject::ckDispose(json)
CkHttp::ckDispose(http)
ProcedureReturn
EndIf
statusCode.i = CkHttp::ckLastStatus(http)
Debug "response status code = " + Str(statusCode)
Debug resp
; Here is a Session log showing the raw HTTP request(s) and response(s).
; Notice how Chilkat automatically fetched the access_token and then used it in the API request.
; ---- Sending Mon, 30 Mar 2026 20:09:44 GMT ----
; POST /connect/token HTTP/1.1
; Host: demo.duendesoftware.com
; Content-Type: application/x-www-form-urlencoded
; Content-Length: 79
; DPoP: ****
;
; client_secret=secret&client_id=m2m.dpop&scope=api&grant_type=client_credentials
; ---- Received Mon, 30 Mar 2026 20:09:44 GMT ----
; HTTP/1.1 200 OK
; Date: Mon, 30 Mar 2026 20:09:43 GMT
; Content-Type: application/json; charset=UTF-8
; Transfer-Encoding: chunked
; Connection: keep-alive
; Server: Kestrel
; Cache-Control: no-store, no-cache, max-age=0
; Pragma: no-cache
; X-REVISION: 4f42f7e7
;
; 374
; {"access_token":"eyJhbGciOiJSUzI1NiIsImtpZCI6IjE1OEUyRDQ1NzhFNDMyOUJBMzRGNzkxQTQ4Nzk5NzYwIiwieDV0IjoiczkyM2RnYnhWbnhoaTJSZmR1cWZyZXZIYUUwIi
; widHlwIjoiYXQrand0In0.eyJpc3MiOiJodHRwczovL2RlbW8uZHVlbmRlc29mdHdhcmUuY29tIiwibmJmIjoxNzc0OTAxMzgzLCJpYXQiOjE3NzQ5MDEzODMsImV4cCI6MTc3NDkwN
; Dk4MywiYXVkIjoiYXBpIiwiY25mIjp7ImprdCI6IlVMR2J4ZXdNOEZUNDhTd3hKeUY3YnJuMDZWMDdPZ3VpdW41d1ZzSVVENEUifSwic2NvcGUiOlsiYXBpIl0sImNsaWVudF9pZCI6
; Im0ybS5kcG9wIiwianRpIjoiQ0Q5MjBFQzQyOTRBOTA3QUUyRjMyQjlCREQzQTA3QzMifQ.bbq8tNMVtJp79Wc9xrAsE0h8oOfdxEKtGZ3qHpJX-mvSS4FVXLoO2bfpq1nhFryfbZkk
; ENfoIwSQjb_35aziq2NrnpWwQ4vDF1b9K-b7eJXPmJybm61DAP9HSE3ZI0jMrMTRFLtjwvE4WV6ZXNTwN0Pzn6teog1i09PZQkaGtlJEBjsBaN5N7OsQvtgFLD0sNkjy1JTw5LMNE7u
; 5-W32qv-XDk9KEGfoBkqyt-SR_A2wI7oZvl4OGnYyX8tUOFszlHZKy0_0V3Oc9wkHRM2JZDA_LfTqldJHR5CF5FuNiJ2hp-kioUA-i0gWxhqnVKVspJqQAN3Z95RDl9jSf-pYSg",
; "expires_in":3600,"token_type":"DPoP","scope":"api"}
; 0
;
;
; ---- Sending Mon, 30 Mar 2026 20:09:44 GMT ----
; GET /api/dpop/test HTTP/1.1
; Host: demo.duendesoftware.com
; Accept: */*
; Accept-Encoding: gzip
; Authorization: ****
; DPoP: ****
;
;
; ---- Received Mon, 30 Mar 2026 20:09:44 GMT ----
; HTTP/1.1 200 OK
; Date: Mon, 30 Mar 2026 20:09:44 GMT
; Content-Type: application/json; charset=utf-8
; Transfer-Encoding: chunked
; Connection: keep-alive
; Server: Kestrel
; X-REVISION: 4f42f7e7
;
; 420
; [{"type":"iss","value":"https://demo.duendesoftware.com"},{"type":"nbf","value":"1774901383"},{"type":"iat","value":"1774901383"},
; {"type":"exp","value":"1774904983"},{"type":"aud","value":"api"},{"type":"cnf","value":"{\u0022jkt\u0022:\u0022ULGbxewM8FT48SwxJyF7brn06V07Oguiun5wVsIUD4E\u0022}"},
; {"type":"scope","value":"api"},{"type":"client_id","value":"m2m.dpop"},{"type":"jti","value":"CD920EC4294A907AE2F32B9BDD3A07C3"},
; {"type":"authorization_scheme","value":"DPoP"},{"type":"proof_token","value":"eyJhbGciOiJFU .... aGrGOgkRXLQ"}]
; 0
;
;
CkPrivateKey::ckDispose(privKey)
CkJsonObject::ckDispose(json)
CkHttp::ckDispose(http)
ProcedureReturn
EndProcedure