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

When a DPoP access token is introspected, additional claims are returned.
  • token_type: Instead of bearer, this value would be DPoP.
  • cnf: The JWK thumbprint confirmation claim returns a hash of the public key.
Clients introspecting the token are expected to validate the access token's DPoP binding by comparing the hash of the public key to the cnf jkt claim in the introspection response.

Sample introspection response:


{
    ...,
    "cnf": {
        "jkt": "yrFAH18WFPml9e9IIo6rB_fLdFX1pdbMgIcd_fW_4aM="
    },
    "token_type": "DPoP",
    ...
}