OAuth 2.0 DPoP Guide
To secure your client apps, implement the Demonstrating-Proof-of-Possession (DPoP) extension for OAuth integrations. DPoP cryptographically binds OAuth access and refresh tokens to your client. To use a token, clients must prove that they own the private key. Even if a token is leaked or stolen, a threat actor can’t use it without the private key.
Required Editions
| Available in: all editions |
DPoP is defined in RFC 9449: OAuth 2.0 Demonstrating Proof of Possession (DPoP). DPoP works by requiring the client to send proof-of-possession in every token and resource request. To do this, the client sends a special JSON Web Token (JWT) called a DPoP proof. The DPoP proof contains the public key, along with some information about the request. The client signs the DPoP proof with the private key. Salesforce can verify the signature and confirm that the DPoP proof isn’t fake or altered.
DPoP Prerequisites
- An OAuth external client app or connected app with the “Require DPoP (Demonstrating Proof-of-Possession)” setting turned on
- A client certificate with a public-private key pair.
- To easily get a certificate, generate a self-signed certificate.
- Alternatively, your client can generate the certificate itself.
Authorization Code Requests
Authorization code flows include the web-server flow, hybrid web-server flow, and
headless identity flows. For these flows, DPoP protects the authorization code by binding it to
the public key. This way, a threat actor can’t use a stolen authorization code and their own key
to request an access token. To bind the authorization code, the authorization request must
include a dpop_jkt parameter with an identifier or “thumbprint” for the public key.
Compute the dpop_jkt from the Public Key
To get the dpop_jkt value, compute the JSON Web Key (JWK) thumbprint of the public key:
- Extract the public key from the client certificate.
- Construct the JWK, a JSON object that represents the public key. Include only the members
required for the key's cryptographic algorithm. See RFC
7638: JSON Web Key (JWK) Thumbprint for detailed descriptions of all members.
Order the members alphabetically and remove all whitespaces and line breaks.
- Rivest-Shamir-Adleman (RSA): e, kty, and n, in that order.
- Elliptic Curve (EC): crv, kty, x, and y, in that order.
- Omit optional members, such as alg and kid, so that the thumbprint reflects the key itself.
- Serialize the JWK to its UTF-8 octets.
- Hash the octets with the SHA-256 algorithm, then base64url-encode the result. This value
is the
dpop_jkt.
Request an Authorization Code
Request an authorization code from the Salesforce /services/oauth2/authorize endpoint. Include the dpop_jkt parameter in the request. Here’s an example request for the web-server flow:
https://MyCompany.my.salesforce.com/services/oauth2/authorize?response_type=code&client_id=<client_id>&
redirect_uri=https://www.mycustomerorderstatus.com/oauth2/callback&dpop_jkt=<JWK thumbprint>DPoP Token Requests
In token requests, DPoP requires your client to send a DPoP proof JWT. To prevent attackers from using stolen requests (replay attacks), the token request must also include a valid nonce value. The nonce is a random value that is only valid for 24 hours. To get the nonce value, your client makes an initial request to the token endpoint, handles an expected error response that includes the nonce, and then retries.
Here’s the token request pattern:
- Your client generates a DPoP proof.
- Your client sends an initial request to the /services/oauth2/token endpoint. The request includes the DPoP proof.
- Salesforce returns an expected error response, which includes a
dpop_noncevalue. Your client handles the error by extracting thedpop_nonce. - Your client generates a new DPoP proof that includes the nonce.
- Your client retries the token request with the new DPoP proof.
- Salesforce grants an access token and any other requested tokens.
Generate an Initial DPoP Proof
The DPoP proof consists of a header and payload. The header contains the public key. The payload contains information about the request.
To create the DPoP proof, use the JWTUtil.generateDPoPProofForToken(httpMethod,
httpUri, dpopCert, nonce, token) Apex method. To use this Apex method,
the client certificate must be saved in the Certificate and Key Management page in
Setup.
Alternatively, you can develop your client app to generate the DPoP proof:
- Generate a header and payload with the required claims.
- Base64-encode the header and payload.
- Cryptographically sign the base64-encoded value with the private key.
For a detailed description of all claims in the DPoP proof, see the DPoP Proof JWTs section in RFC 9449: OAuth 2.0 Demonstrating Proof of Possession (DPoP).
Here's an example header:
{
"alg": "ES256",
"jwk": {
"crv": "P-256",
"kty": "EC",
"x": "vCAiGjuhIeMCTEJq_N9-oJRjPTRENezkoQ5TBaJtK7Y",
"y": "YXWanpPri_6o2virYzsAfVNZhAW3SQaxFSBcs89BC-Q"
},
"typ": "dpop+jwt"
}Here's an example payload:
{
"jti": "EHFHE41EbRagW440IfHkUwVo",
"htm": "POST",
"htu": "https://MyCompany.my.salesforce.com/services/oauth2/token",
"iat": 1787773196
}Send an Initial Request to the Token Endpoint
The initial request must include a DPoP header containing the DPoP proof. Here’s an example request for the web-server flow:
POST /services/oauth2/token HTTP/1.1
Host: MyCompany.my.salesforce.com
Content-type: application/x-www-form-urlencoded
Accept: application/json
DPoP: eyJ0eX...
grant_type=authorization_code&
code=aPrx5…&
client_id=<client_id>&
client_secret=<client_secret>&
redirect_uri=https://www.mycustomerorderstatus.com/services/oauth2/successHandle the DPoP Nonce Error
As expected, Salesforce returns a “400 Bad Request” error response. The response includes a
DPoP-Nonce header that contains a nonce value. Your client must extract
the value from the DPoP-Nonce header.
HTTP/1.1 400 Bad Request
DPoP-Nonce: eyJ7S_z...
{
"error": "use_dpop_nonce",
"error_description":
"Authorization server requires nonce in DPoP proof"
}Generate a DPoP Proof with the Nonce Value
Before retrying the token request, generate a new, unique DPoP proof. This time, include a nonce claim with the nonce value.
Because the nonce is valid for only 24 hours, your client-side code must keep it fresh. If
the nonce has expired, the token request returns a use_dpop_nonce error
with a new nonce. Make sure that your client-side code handles this error by extracting the
new nonce.
{
"jti": "YjMja9CviFSJLSG5tO_K5Gti",
"htm": "POST",
"htu": "https://MyCompany.my.salesforce.com/services/oauth2/token",
"iat": 1787773196,
"nonce": "eyJ7S_z..."
}Retry the Token Request
Now that you have the nonce embedded into a new DPoP proof, retry the token request.
POST /services/oauth2/token HTTP/1.1
Host: MyCompany.my.salesforce.com
Content-type: application/x-www-form-urlencoded
Accept: application/json
DPoP: <new DPoP proof with valid nonce>
grant_type=authorization_code&
code=aPrx5…&
client_id=<client_id>&
client_secret=<client_secret>&
redirect_uri=https://www.mycustomerorderstatus.com/services/oauth2/successGet a DPoP-Bound Access Token
If the request is successful, Salesforce returns a DPoP-bound access token. Refresh tokens,
if requested, are also DPoP-bound. The token_type for this response is
DPoP.
{
"access_token": "00D...",
"refresh_token": "5Ae...",
"signature": "/5Z...",
"scope": "refresh_token web id api",
"instance_url": "https://MyCompany.my.salesforce.com",
"id":"https://login.salesforce.com/id/00DB0000000TfcRMAS/005B0000005Bk90IAC",
"token_type": "DPoP",
"issued_at": "1787761167317"
}Resource Requests
When you have a DPoP-bound access token, use it to request access to Salesforce resources.
Generate a DPoP Proof with the Hashed Access Token
Your client computes the SHA-256 hash of the access token. It then generates a new DPoP proof with the hashed access token in the ath payload claim. The DPoP proof must also include a valid nonce value. Here’s an example payload:
{
"jti": "uzUwriQThuMBtwG9CSQxHHMY",
"htm": "POST",
"htu": "https://MyCompany.my.salesforce.com/services/oauth2/userinfo",
"iat": 1787773633,
"ath": "LT0...",
"nonce": "ni4..."
}Request Access to Salesforce Resources
To access Salesforce data, send a request to the resource endpoint with these headers:
Authorization: DPoP <DPoP-bound access token>DPoP: <DPoP proof JWT with hashed token in ath claim>
Here’s an example request for user information:
POST /services/oauth2/userinfo HTTP/1.1
Host: MyCompany.my.salesforce.com
Authorization: DPoP 00DSG00000slUEr!AQEA...PmxpTx.hs
DPoP: <new DPoP proof with hashed access token & valid nonce>
Content-Type: application/x-www-form-urlencoded
Accept: application/jsonUse DPoP-Bound Refresh Tokens
To use a DPoP-bound refresh token, the request to the /services/oauth2/token endpoint must include a unique DPoP proof with a valid nonce value. If the nonce has expired, the call returns a use_dpop_nonce value with a new nonce.
POST /services/oauth2/token HTTP/1.1
Host: MyCompany.my.salesforce.com
DPoP: <new DPoP proof with valid nonce>
grant_type=refresh_token&
client_id=3MVG9lKcPoNINVBIPJjdw1J9LLM82HnFVVX19KY1uA5mu0QqEWhqKpoW3svG3XHrXDiCQjK1mdgAvhCscA9GE&client_secret=1955279925675241571&
refresh_token=your token hereOAuth 2.0 DPoP Supported Flows and Endpoints
The OAuth 2.0 Demonstrating Proof-of-Possession (DPoP) extension protects access to your Salesforce data. It works by cryptographically binding OAuth access and refresh tokens to the client that requested them. Even if an attacker gets access to a token, they can’t use it to access your data. You can use DPoP with most OAuth flows, and you can use DPoP-bound access tokens to request access to Salesforce resources for supported endpoints.
Required Editions
| Available in: all editions |
OAuth Flows
These OAuth 2.0 flows support DPoP:
- Client credentials flow
- Device flow
- Headless login, registration, passwordless login, and guest flows
- Hybrid web-server and refresh token flows
- JWT bearer flow
- Refresh token flow
- Token exchange flow
- SAML bearer assertion flow
All other flows, including the username-password, user-agent, and hybrid-user agent flows, aren’t supported.
Endpoints
Salesforce supports DPoP for My Domain and Experience Cloud sites. You can use DPoP-bound access tokens to access these endpoints:
