Demonstrating Proof of Possession at the application layer
This demo is broken down into the individual steps of the DPoP flow to show how the mechanism works.
In a real-life production scenario the mechanism will be handled by a library, and the steps will not be noticeable to the user.
PS! The Authorization Server (Duende IdentityServer) and the API are running on "cold" and shared Azure instances so you might have to wait a while for the requests to complete.
The first step in the DPoP flow is to generate the DPoP proof.
The DPoP proof is a Json Web Token that contains some specific information in both the jose header and the payload that is used in the DPoP flow.
BASE64URL ENCODED TOKEN
JSON FORMATTED TOKEN
Jose header:
Payload:
Signature:
As you can see above in the DPoP proof that just got generated, the jose header contains a claim with a jwk structure which contains neccessary information about the key which was used to sign the DPoP proof.
This key will be used by the Authorization Server to bind the DPoP proof to the access token.
The payload in the JWT contains claims that will be used to bind the proof to a resource.
These claims are:
"htm": Indicates the http method that the client will use to request the resource.
"htu": The http uri of the resource that the client wants to request (without query and fragment parts).
The second step in the DPoP flow is to request an Access Token from the Authorization Server. The client now adds the DPoP proof it created in the previous step to the request it makes to Duende IdentityServer.
The DPoP proof is added to the DPoP http header in the request.
Duende IdentityServer will grab the public key from the jwk in the DPoP proof it received, and will then bind the DPoP proof to the Access Token by adding a thumbprint in the payload of the JWT.
The thumbrint in the JWT Access Token is placed in the JWT payload:
"jkt": The thumbprint of the public key in the DPoP proof.
BASE64URL ENCODED TOKEN
JSON FORMATTED TOKEN
Jose header:
Payload:
Signature:
The API must be able to verify that the DPoP proof is bound to the Access Token it receives from the client.
So, the third step in the DPoP flow is to calculate the hash of the Access Token that the client received from the Authorization Server.
When we have the hash we need to include it the new DPoP proof that will be generated for the API.
The Access Token hash:
Now that we have the Access Token hash we can create the new DPoP proof.
The DPoP proof we create for the API will contain a claim called ath. The claim value is the hash of the Access Token.
We will use the new DPoP proof when we request the resource at the API in combination with the Access Token, and these tokens are now bound together.
To be more specific: the Access Token is bound to the public key in the DPoP proof, and the DPoP proof is bound to the Access Token..
BASE64URL ENCODED TOKEN
JSON FORMATTED TOKEN
Jose header:
Payload:
Signature:
Now that we have both the Access Token and the new DPoP proof which are cryptographically bound to each other, we can attach the tokens when we request the resource at the API.
We add the two tokens as the http header values "Authorization" and "DPoP".
The API will receive the following:
An Access Token that contains the thumbprint of the public key from the DPoP proof used in the token request
A new DPoP proof that contains the same public key that was used when calling Duende IdentityServer