Demonstrating Proof-of-Possession (DPoP)
DPoP provides a mechanism for a client to get sender-constrained OAuth tokens by providing a proof-of-possession of a public-private key pair.
The specification is in draft status: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-dpop-11.
DPoP HTTP header
The DPoP header is a signed JWT that includes key information for proof-of-possession.
The JOSE header of the DPoP JWT must contain at least the following parameters:
| Parameter | Description |
|---|---|
| typ | The type of JWT. This value must be "dpop+jwt". |
| alg | The signature algorithm. The valid values are: RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512 |
| jwk | A JSON Web Key (JWK) representing the public key. Must not contain a private key. |
JOSE header example:
{
"typ": "dpop+jwt",
"alg": "RS256",
"jwk": {
"kty": "RSA",
"n": "...",
"e": "AQAB"
}
}
The payload of the DPoP JWT must contain at least the following claims:
| Claim name | Description |
|---|---|
| jti | Unique identifier for this JWT. |
| htm | The HTTP method of the request to which the JWT is attached. |
| htu | The HTTP target URI, without query and fragment parts. This would be the Pushed Authorization Request (PAR) endpoint or token endpoint. |
| iat | Creation timestamp of the JWT. |
JWT payload example:
{
"jti": "3765f59c-43cd-4cf8-8180-73bc9ae4ff3c",
"htm": "POST",
"htu": "https://<tenant-hostname>/oauth2/token",
"iat": 1661847227
}
The DPoP JWT must be signed with a private key, and the signature must be verifiable with the
public key that is supplied in the JOSE header of the JWT. The JWT must not be valid for more than
30 minutes. If there is no exp claim, the expiry for this JWT is assumed to be 30
minutes after the creation timestamp specified in iat.
The JWT is sent in a header named DPoP.
Request example:
curl -ki https://<tenantId>/oauth2/token
-H "DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6Il..."
-d "grant_type=client_credentials&client_secret=<secret>&client_id=<clientId>"
Authorize request
The authorization request includes a thumbprint of the JSON Web Key, in the query parameter or POST body.
| Parameter | Description |
|---|---|
| dpop_jkt | Thumbprint of the JSON Web Key to be associated with this authorization request. |
https://<tenantId>/oauth2/authorize?grant_type=authorization_code&client_id=<clientId>&redirect_uri=<redirect_uri>&dpop_jkt=<dpop_jkt>&...
The authorization code generated for this request would be bound to that thumbprint and used for validation at the token request. This prevents the authorization code from being stolen and used by something else that does not have the same private key to sign the token request when exchanging the code for tokens.
It is recommended to use Pushed Authorization Requests (PAR) or request objects in conjuncture with DPoP, to send the
dpop_jkt thumbprint in a secure way. Alternatively, with PAR, the DPoP header can
be used instead of dpop_jkt. See DPoP HTTP header section above
for details. Pushed authorization requests can be enforced by selecting Require pushed
authorization request (PAR) when configuring the OpenID Connect application.
Token Request
Access and refresh token requests are required to have a DPoP HTTP header. See DPoP HTTP
header section above for details.
For authorization code flow, the thumbprint of this JWK must match the JWK thumbprint (dpop_jkt) that was sent during the authorization request.
Request example:
curl -ki https://<tenantId>/oauth2/token
-H "DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6Il..."
-d "grant_type=client_credentials&client_secret=<secret>&client_id=<clientId>"
The result of performing this flow is a set of tokens that are bound to the proof-of-possession. This can be validated with token introspection.
Token introspection
- token_type: Instead of bearer, this value would be DPoP.
- cnf: The JWK thumbprint confirmation claim returns a hash of the public key.
Sample introspection response:
{
...,
"cnf": {
"jkt": "yrFAH18WFPml9e9IIo6rB_fLdFX1pdbMgIcd_fW_4aM="
},
"token_type": "DPoP",
...
}