On this page
Configure OAuth 2.0 Demonstrating Proof-of-Possession
This guide discusses how to create sender-constrained access tokens that are an app-level mechanism for preventing token replays at different endpoints.
Learning outcomes
- Understand the purpose of Demonstrating Proof-of-Possession
- Understand how to configure OAuth 2.0 Demonstrating Proof-of-Possession (DPoP) for your org
What you need
Glitch (opens new window) project or account
An OAuth 2.0 client app that has the Require Demonstrating Proof of Possession (DPoP) header in token requests checkbox enabled.
If you're using the API, add the DPoP parameter (opens new window) (dpop_bound_access_tokens: true
) tosettings.oauthClient
to your app.Be able to build a request and obtain an access token for your app.
Be able to create a JSON Web Key (opens new window). In a production environment, use your internal instance of a key pair generator to generate the JWK for use with DPoP. See this key pair generator (opens new window) for an example. For testing purposes only, you can use this simple JWK generator (opens new window) to generate a key pair for an example setup. Use only asymmetric keys (opens new window) with DPoP.
Note: The JWK that's used for DPoP authentication is separate from the JWK used for client authentication.
Overview
OAuth 2.0 Demonstrating Proof-of-Possession (DPoP) helps prevent unauthorized parties from using leaked or stolen access tokens. When you use DPoP, you create an application-level mechanism to sender-constrain both access and refresh tokens. This helps prevent token replays at different endpoints.
Note: The Okta DPoP feature is based on the current RFC (opens new window).
DPoP enables a client to prove possession of a public/private key pair by including a DPoP header in a /token
endpoint request. The value of the DPoP header is a JSON Web Token (JWT) and is called a DPoP proof. This DPoP proof enables the authorization server to bind issued tokens to the public part of a client's key pair. Recipients of these tokens (such as an
OAuth 2.0 DPoP JWT flow
Before you configure DPoP
Create a DPoP proof JWT (opens new window). A DPoP proof JWT includes a header and payload with claims. Then, sign the JWT with the private key from your JSON Web Key (JWK). Use the DPoP proof JWT to obtain a DPoP-bound access token. To create a DPoP proof JWT, use your internal instance to sign the JWT for a production org. See this JWT generator (opens new window) for an example of how to make and use JWTs in Node.js apps. For testing purposes only, you can use this JWT tool (opens new window) to build, sign, and decode JWTs.
DPoP proof parameters and claims
Include the following required parameters in the JWT header:
typ
: Type header. Declares that the encoded object is a JWT and meant for use with DPoP. This must bedpop+jwt
.alg
: Algorithm. Indicates that the asymmetric algorithm is RS256 (RSA using SHA256). This algorithm uses a private key to sign the JWT and a public key to verify the signature. Must not benone
or an identifier for a symmetric algorithm. This example usesRS256
.jwk
: JSON Web Key. Include the public key (in JWK string format). Okta uses this public key to verify the JWT signature. See the Application JSON Web Key Response properties (opens new window) for a description of the public key properties.
Example JWT header
{
"typ": "dpop+jwt",
"alg": "RS256",
"jwk": {
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"kid": "XUl71vpgPXgxSTCYHbvbEHDrtj-adpVcxXH3TKjKe7w",
"alg": "RS256",
"n": "4LuWNeMa7.....zLvDWaJsF0"
}
}
Include the following required claims in the JWT payload:
Configure DPoP
This section discusses the initial POST /token
request that you need to make, the JWT payload update, and the second POST /token
request that includes the updated JWT.
Make the initial request. Include the additional
DPoP
header (--header 'DPoP: eyJ0eXAiOiJkcG9w.....H8-u9gaK2-oIj8ipg'
) in your/token
request. The value for the DPOP header is the DPoP proof JWT from the Before you configure DPoP section.The
dpop-nonce
header and value are included in the headers of that response. The authorization server provides thedpop-nonce
value to limit the lifetime of DPoP proof JWTs and renews the value every 24 hours. The olddpop-nonce
value continues to work for three days after generation. Be sure to save thedpop-nonce
value from the token response header and refresh it every 24 hours.Example response
HTTP/ 1.1 400 Bad Request Cache-Control: no-cache, no-store Pragma: no-cache Content-Type: application/json Server: nginx Date: Tue, 07 Mar 2023 23:43:13 GMT dpop-nonce: 8NLZUUhVawx1ns8AjrC4F6j8D2phvaw7 { "error": "use_dpop_nonce", "error_description": "Authorization server requires nonce in DPoP proof." }
Update the JWT payload.
- Add the
dpop-nonce
header value from the response as thenonce
claim value. - Include a
jit
claim, which is a unique JWT identifier (opens new window) for the request.
Example payload:
- Add the
Copy the new DPoP proof and add it to the DPoP header in the second POST
/token
request for an access token. TheExample response
Note: Tokens are truncated for brevity.
{ "token_type": "DPoP", "expires_in": 3600, "access_token": "eyJraWQiOiJRVX.....wt7oSakPDUg", "scope": "openid offline_access", "refresh_token": "3CEz0Zvjs0eG9mu4w36n-c2g6YIqRfyRSsJzFAqEyzw", "id_token": "eyJraWQiOiJRVXlG.....m5h5-NAtVFdwD1bg2JprEJQ" }
Decode the access token
You can use the JWT tool (opens new window) to decode the access token to view the included claims. The decoded access token should look something like this:
Claims
cnf
: Confirmation. Claim that contains the confirmation method.jkt
: JWK confirmation method. A base64url encoding of the JWK SHA-256 hash of the DPoP public key (in JWK format) to which the access token is bound.
Note: If your client has DPoP enabled, then you can't add or modify the
cnf
claim using token inline hooks.
Make a request to a DPoP-protected resource
Now that you have a DPoP-bound access token, you can make requests to DPoP-protected resources.
Validate token and DPoP header
The resource server must perform validation on the access token to complete the flow and grant access. When the client sends an access request with the access token, validation should verify that the cnf
claim is present. Then validation should compare the jkt
in the access token with the public key in the JWT value of the DPoP
header.
The following is a high-level overview of the validation steps that the resource server must perform.
Note: The resource server must not grant access to the resource unless all checks are successful.
Refresh an access token
To refresh your DPoP-bound access token, send a token request with a grant_type
of refresh_token
. Then, include the same DPoP
header value that you used to obtain the refresh token in the DPoP
header for this request. Include the openid
scope when you also want to refresh an ID token. In the following examples, tokens are truncated for brevity.
Example request
Example response
{
"token_type": "DPoP",
"expires_in": 3600,
"access_token": "eyJraWQiOiJRVXlGdjB.....RxDhLJievVVN5WQrAZlw",
"scope": "offline_access openid",
"refresh_token": "3CEz0Zvjs0eG9mu4w36n-c2g6YIqRfyRSsJzFAqEyzw",
"id_token": "eyJraWQiOiJRVX.....3SA6LTm7mA"
}