Illustrated DPoP (OAuth Access Token Security Enhancement)

Takahiko Kawasaki
8 min readApr 29, 2020

--

Introduction

This article explains a specification called “DPoP”, OAuth 2.0 Demonstration of Proof-of-Possession at the Application Layer.

The specification defines a mechanism to prevent illegal API calls from succeeding only with a stolen access token.

In the traditional mechanism, API access is allowed only if the access token presented by the client application is valid. However, if a mechanism of PoP (Proof of Possession) such as DPoP is employed, the API implementation additionally checks whether the client application presenting the access token is the valid owner of the access token (= whether the client application is the same one that the access token has been issued to). If the client is not the valid owner of the access token, the API access is rejected.

The section 3 of RFC 8705 defines a PoP mechanism known as “MTLS”, which is better than DPoP and should be employed if possible. However, if MTLS cannot be used, for example, in the case of SPA (Single Page Application) that runs in a web browser, DPoP is a candidate for PoP.

Let’s start learning DPoP.

1. Illustrated DPoP

There are a client, an authorization server and a resource server. Regarding the relationship among them, please read “The Simplest Guide To OAuth 2.0”.

(1) Generate a key pair of public key and private key.

(2) Prepare a JSON that includes the generated public key and a payload. The JSON will be used as the header of the JWT which will be generated later. The payload corresponds to a token request, but it is not the token request itself.

(3) Create signature for the data prepared in the step (2) using the private key.

(4) Pack them into a JWT. This JWT is called “DPoP proof JWT”.

(5) Make a token request which includes the DPoP proof JWT.

(6) The implementation of the token endpoint extracts the DPoP proof JWT from the token request.

(7) Verify the signature using the public key included in the DPoP proof JWT itself. The verification ensures that the client application has the private key which corresponds to the public key.

(8) Generate an access token.

(9) Bind the public key to the generated access token. The way of the binding is explained later.

(10) Issue the access token to the client application.

(11) In DPoP, one extra process is needed before an API call is made. Prepare a JSON that includes the public key generated in the step (1) and a payload that corresponds to the API call being made.

(12) Create signature for the data prepared in the step (11) using the private key.

(13) Pack them into a JWT. This JWT also is a DPoP proof JWT.

(14) Make an API call that includes the access token and the DPoP proof JWT generated in the step (13).

(15) The implementation of the API extracts the access token and the DPoP proof JWT from the request.

(16) Verify the signature using the public key included in the DPoP proof JWT itself. The verification ensures that the client application has the private key which corresponds to the public key.

(17) Check whether the public key included in the DPoP proof JWT matches the one that is bound to the access token. If they match, it means that the client making the API call is the valid owner of the access token (= the client that the access token has been issued to). As a result, the API call is accepted. In contrast, if they don’t match, the API call is rejected.

The following is a full-color diagram of DPoP.

DPoP diagram

2. Binding between Access Token and Public Key

Binding a public key to an access token is achieved by remembering the hash value of the public key as one attribute of the access token. The hash value is “JWK SHA-256 Thumbprint” defined in RFC 7638.

If an access token is bound to a public key, an introspection request (RFC 7662) for the access token will receive a JSON that includes the hash value of the public key. To be concrete, the base64url expression of the JWK SHA-256 Thumbprint of the public key is included as the value of the jkt claim under the cnf claim.

"cnf": {
"jkt": "0ZcOCORZNYy-DWpqq30jZyJGHTN0d2HglBV3uiguA4I"
}

When the format of the access token is JWT, the payload part includes the hash value as the value of the jkt claim under the cnf claim in the same way.

3. DPoP Proof JWT

3.1. DPoP Proof JWT Header

The specification for the header part of DPoP proof JWT is as follows.

  • typ — A fixed string, "dpop+jwt".
  • alg — A signature algorithm. An identifier in the table in the section 3.1 of RFC 7518. "none" and symmetric algorithms are not allowed.
  • jwk — A public key that the client chooses.

The following is an example of DPoP proof JWT header excerpted from the section 4 of the DPoP specification.

{
"typ":"dpop+jwt",
"alg":"ES256",
"jwk": {
"kty":"EC",
"x":"l8tFrhx-34tV3hRICRDY9zCkDlpBhF42UQUfWVAWBFs",
"y":"9VE4jf_Ok_o64zbTTlcuNJajHmt6v9TDVrU0CdvGRDA",
"crv":"P-256"
}
}

3.2. DPoP Proof JWT Payload

The specification for the payload part of DPoP proof JWT is as follows.

  • jti — A unique identifier of the JWT.
  • htm — The HTTP method of the request.
  • htu — The URL of the request.
  • iat — The time at which the JWT is issued.

The following is an example of DPoP proof JWT payload excerpted from the section 4 of the DPoP specification.

{
"jti":"-BwC3ESc6acc2lTc",
"htm":"POST",
"htu":"https://server.example.com/token",
"iat":1562262616
}

4. Token Request

As explained so far, to generate an access token bound to a public key, a DPoP proof JWT needs to be included in a token request. The way of inclusion is to set a DPoP proof JWT as the value of the DPoP HTTP header.

The following is an example of token request excerpted from the section 5 of the DPoP specification.

POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6Ik
VDIiwieCI6Imw4dEZyaHgtMzR0VjNoUklDUkRZOXpDa0RscEJoRjQyVVFVZldWQVdCR
nMiLCJ5IjoiOVZFNGpmX09rX282NHpiVFRsY3VOSmFqSG10NnY5VERWclUwQ2R2R1JE
QSIsImNydiI6IlAtMjU2In19.eyJqdGkiOiItQndDM0VTYzZhY2MybFRjIiwiaHRtIj
oiUE9TVCIsImh0dSI6Imh0dHBzOi8vc2VydmVyLmV4YW1wbGUuY29tL3Rva2VuIiwia
WF0IjoxNTYyMjYyNjE2fQ.2-GxA6T8lP4vfrg8v-FdWP0A0zdrj8igiMLvqRMUvwnQg
4PtFLbdLXiOSsX0x7NVY-FNyJK70nfbV37xRZT3Lg

grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
&code_verifier=bEaL42izcC-o-xBk0K2vuJ6U-y1p9r_wW2dFWIWgjz-

If the authorization server supports DPoP, the public key included in the DPoP proof JWT will be bound to the access token being generated. And, the value of the "token_type" in the token response will become "DPoP".

In contrast, if the authorization server doesn’t support DPoP, the DPoP HTTP header will be ignored and the public key won’t be bound to the access token. In this case, the value of the "token_type" in the token response will become other value, typically "Bearer", than "DPoP".

5. API Call

The way of including a DPoP proof JWT in an API call is the same for token requests. That is, the DPoP HTTP header is used.

On the other hand, there is a small difference when the Authorization header is used to include an access token.

In the traditional way defined in the section 2.1 of RFC 6750, an access token is passed as shown below.

Authorization: Bearer {Access-Token}

However, when the access token is bound to a public key, DPoP is used instead of Bearer like below.

Authorization: DPoP {Access-Token}

The following is an example of API call excerpted from the section 6 of the DPoP specification.

GET /protectedresource HTTP/1.1
Host: resource.example.com
Authorization: DPoP eyJhbGciOiJFUzI1NiIsImtpZCI6IkJlQUxrYiJ9.eyJzdWI
iOiJzb21lb25lQGV4YW1wbGUuY29tIiwiaXNzIjoiaHR0cHM6Ly9zZXJ2ZXIuZXhhbX
BsZS5jb20iLCJhdWQiOiJodHRwczovL3Jlc291cmNlLmV4YW1wbGUub3JnIiwibmJmI
joxNTYyMjYyNjExLCJleHAiOjE1NjIyNjYyMTYsImNuZiI6eyJqa3QiOiIwWmNPQ09S
Wk5ZeS1EV3BxcTMwalp5SkdIVE4wZDJIZ2xCVjN1aWd1QTRJIn19.vsFiVqHCyIkBYu
50c69bmPJsj8qYlsXfuC6nZcLl8YYRNOhqMuRXu6oSZHe2dGZY0ODNaGg1cg-kVigzY
hF1MQ
DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6Ik
VDIiwieCI6Imw4dEZyaHgtMzR0VjNoUklDUkRZOXpDa0RscEJoRjQyVVFVZldWQVdCR
nMiLCJ5IjoiOVZFNGpmX09rX282NHpiVFRsY3VOSmFqSG10NnY5VERWclUwQ2R2R1JE
QSIsImNydiI6IlAtMjU2In19.eyJqdGkiOiJlMWozVl9iS2ljOC1MQUVCIiwiaHRtIj
oiR0VUIiwiaHR1IjoiaHR0cHM6Ly9yZXNvdXJjZS5leGFtcGxlLm9yZy9wcm90ZWN0Z
WRyZXNvdXJjZSIsImlhdCI6MTU2MjI2MjYxOH0.lNhmpAX1WwmpBvwhok4E74kWCiGB
NdavjLAeevGy32H3dbF0Jbri69Nm2ukkwb-uyUI4AUg1JSskfWIyo4UCbQ

6. Authlete

Authlete supports DPoP since version 2.2. To support DPoP, some request parameters for DPoP have been added to some Authlete APIs.

/api/auth/token API

  • dpop — The value of the DPoP HTTP header included in the token request.
  • htm — The HTTP method of the token request. The default value is POST.
  • htu — The URL of the token endpoint. The default value is tokenEndpoint of the service.

By passing the value of the DPoP HTTP header of a token request to /api/auth/token API, Authlete takes care of the process necessary to bind the public key to the access token being generated. That’s all for generation of DPoP-aware access tokens.

/api/auth/userinfo API

  • dpop — The value of the DPoP HTTP header included in the userinfo request.
  • htm — The HTTP method of the userinfo request. In normal cases, the value is either GET or POST.
  • htu — The URL of the userinfo endpoint. The default value is userInfoEndpoint of the service.

By passing the value of the DPoP HTTP header of a userinfo to /api/auth/userinfo API, Authlete takes care of the verification process necessary for DPoP.

/api/auth/introspection API

  • dpop — The value of the DPoP HTTP header included in the API call to the protected resource endpoint.
  • htm — The HTTP method of the API call to the protected resource endpoint.
  • htu — The URL of the protected resource endpoint.

Authlete’s /api/auth/introspection API is different from the standard one defined in RFC 7662. The API not only returns information about the access token but also performs various verification steps and generates an error message conforming to RFC 6750 as necessary. By passing parameters related to DPoP (dpop , htm and htu) to the introspection API, Authlete takes care of all the verification related to DPoP. That is, Authlete performs “signature verification” and “verification of binding between the access token and the public key” that are performed in the step (17). Therefore, implementations of protected resource endpoints become much easier with Authlete’s introspection API than with the standard one.

Finally

I introduced DPoP, one of PoP mechanisms. Consider adoption of DPoP when you need an application-level PoP mechanism.

--

--

Takahiko Kawasaki

Co-founder and representative director of Authlete, Inc., working as a software engineer since 1997. https://www.authlete.com/