Sample code for 30+ languages & platforms
Delphi DLL Requires Chilkat v11.4.0+

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 automatically add the following headers to HTTP requests:

Authorization: DPoP <access_token>
DPoP: <new JWT signed with same key>

Chilkat Delphi DLL Downloads

Delphi DLL
var
success: Boolean;
privKey: HCkPrivateKey;
tokenEndpoint: PWideChar;
apiEndpoint: PWideChar;
json: HCkJsonObject;
http: HCkHttp;
resp: PWideChar;
statusCode: Integer;

begin
success := False;

privKey := CkPrivateKey_Create();

//  Load the private key for DPoP.  Chilkat v11.4.0 and later accept an EC key (P-256, P-384 or P-521).
//  Chilkat v11.7.0 and later also accept an RSA key (2048 bits or more) or an Ed25519 key.
success := CkPrivateKey_LoadAnyFormatFile(privKey,'qa_data/pem/ecc_privKey.pem','');
if (success = False) then
  begin
    Memo1.Lines.Add(CkPrivateKey__lastErrorText(privKey));
    Exit;
  end;

tokenEndpoint := 'https://demo.duendesoftware.com/connect/token';
apiEndpoint := '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 := CkJsonObject_Create();
CkJsonObject_UpdateString(json,'client_secret','secret');
CkJsonObject_UpdateString(json,'client_id','m2m.dpop');
CkJsonObject_UpdateString(json,'token_endpoint',tokenEndpoint);
CkJsonObject_UpdateString(json,'scope','api');

http := CkHttp_Create();
CkHttp_putSessionLogFilename(http,'c:/aaworkarea/sessionLog.txt');

//  Provide private key to be used with DPoP
success := CkHttp_SetDPoPKey(http,privKey);
if (success = False) then
  begin
    Memo1.Lines.Add(CkHttp__lastErrorText(http));
    Exit;
  end;

CkHttp_putAuthToken(http,CkJsonObject__emit(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 := CkHttp__quickGetStr(http,apiEndpoint);
if (CkHttp_getLastMethodSuccess(http) = False) then
  begin
    Memo1.Lines.Add(CkHttp__lastErrorText(http));
    Exit;
  end;

statusCode := CkHttp_getLastStatus(http);

Memo1.Lines.Add('response status code = ' + IntToStr(statusCode));
Memo1.Lines.Add(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_Dispose(privKey);
CkJsonObject_Dispose(json);
CkHttp_Dispose(http);