DPoP #1: Sender Constraining OAuth Access Tokens with DPoP

Nope, DPop is not a new sexy music genre from Deutschland, Denmark or Djibouti. Personally, I do find DPoP quite exciting. So, you might want to read on - even if you came here looking for the next big thing in music.

In this series of three posts I will take a look at the DPoP draft from the IETF OAuth WG, and see if I am able to implement the specification using my not so super programming skills.

The problem of token theft

With OAuth 2.0 the API outsources the job of establishing trust to its clients to the Authorization Server. The API relies on the Authorization Server to ensure that the Access Token is issued only to clients that have been granted the right to act on behalf of the user, and that the client is authorized to access the resource.

With the awesome power that the holder of an Access Token possesses, it is clearly a weak link that Access Tokens by specification are Bearer tokens.

“[A bearer token is] a security token with the property that any party in possession of the token (a “bearer”) can use the token in any way that any other party in possession of it can. Using a bearer token does not require a bearer to prove possession of cryptographic key material (proof-of-possession).”

Bearer tokens have been used in OAuth 2.0 flows since the framework was published, so what’s the problem? Well, one of the more obvious shortcomings of bearer tokens is the risk of tokens being stolen and misused. And basically, if the consequences of stolen tokens are high, the use of bearer token just doesn’t cut it. In high risk scenarios the party which is responsible for the data that is exposed through the API would probably require a bearer to prove the possession of cryptographic key material. Or, sender constraining tokens, as some people call it - meaning that use of the token is restricted to the party who was granted access.

You see, a bearer token is comparable to cash: if someone steals your hard earned 100$ bill they can spend it all on cheap vodka at the liquor shop without the cashier ever knowing that it’s actually your money, nor does he know the fact that you’d never spend money on cheap liquor. Only the fine stuff, right?

The token is mine, and only mine!

So how can we mitigate the risk of token theft? Well, the API should ideally be able to somehow verify that the access token was issued to the same client that sends the request to the API. There are several proposed solutions for how this could be accomplished, and most of them describe a way of cryptographically binding the access token to some secret that only the client knows. And then to have the client use the same secret when calling the API. Thus making the API capable of verifying that the same secret was used both at the AS and the API.

OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens

The IETF WG has described a way of binding Access Token to clients by leveraging existing TLS implementations. Not only does it present an elegant solution for the need to bind the Access Token to the client, it also provides a way to strongly authenticate the client by using the underlying TLS connection.

The mTLS spec is now a Proposed Standard which means that the mechanism will be available in many Authorization Server products. The mTLS approach really makes a lot of sense, and seems to be the obvious choice to mitigate the problem.

…Well, this is true as long as the client can keep the private key that belongs to the client certificate a secret.

Oh… AND, if you are able to give the user a decent user experience - which does not involve the user selecting the correct certificate from a obscure list that pops up on his screen..

Oh… AND, when using ephemeral certificates (created on the fly) - as long as the web server permits self-signed certificates.

Oh… AND, if you want to use mTLS for client authentication - as long as it’s practically feasible to create, deploy and manage client certificates for potentially thousands of clients.

Oh… AND, as long as TLS isn’t prematurely terminated by some proxy you aren’t in control of.

Sadly, this isn’t always the case.

Oh.. Let me rephrase: this is quite often not the case.

A compelling alternative

OAuth 2.0 Demonstrating Proof-of-Possession at the Application Layer (DPoP)

Some very clever people at IETF realized that the mTLS mechanism is a bad fit for some application types like SPAs and mobile apps. So they came up with an alternative solution, and named it DPoP.

The DPoP proof

The specification describes how a client can prove that it is in possession of a private key (DPoP Proof) that is used when requesting a token from an Authorization Server.

The DPoP proof looks like this (pay extra special notice to the “typ” claim, and some newbies in the payload):

{
  "typ":"dpop+jwt",
  "alg":"ES256",
  "jwk": {
    "kty":"EC",
    "x":"l8tFrhx-34tV3hRICRDY9zCkDlpBhF42UQUfWVAWBFs",
    "y":"9VE4jf_Ok_o64zbTTlcuNJajHmt6v9TDVrU0CdvGRDA",
    "crv":"P-256"
  }
}
.
{
  "jti":"-BwC3ESc6acc2lTc",
  "htm":"POST",
  "htu":"https://server.example.com/token",
  "iat":1562262616
}

The DPoP HTTP Header and the Authorization Server

The DPoP proof is transferred to the Authorization Server by adding a new HTTP Header called DPoP, and it goes a little something like this:

POST /token HTTP/1.1
  Host: server.example.com
  Content-Type: application/x-www-form-urlencoded;charset=UTF-8
  DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6Ik
   VDIiwieCI6Imw4dEZyaHgtMzR0VjNoUklDUkRZOXpDa0RscEJoRjQyVVFVZldWQVdCR
   ...

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

After receiving the token request with the DPoP proof the Authorization Server can validate and bind the proof to the Access Token that the client requested - the Access Token now includes the new claim “jkt” which holds a value that uniquely identifies the proof that was presented.

{
  "sub": "someone@example.com",
  "iss": "https://server.example.com",
  "nbf": 1562262611,
  "exp": 1562266216,
  "cnf": { "jkt" : "0ZcOCORZNYy-DWpqq30jZyJGHTN0d2HglBV3uiguA4I" }
}

After the token is created it is returned to the client just like in the normal flow.

Requesting the API with DPoP

When the client makes a request to the API it includes the Access Token in the Authorization Header like usual, but in addition it adds a new DPoP proof which is constructed using the same key material as the DPoP proof for the token request made to the Authorization Server. The new DPoP proof includes a hash of the Access Token that the client received from the Authorization Server, binding the access token to the DPoP proof in order to make sure that the DPoP proof is tightly bound to this particular grant.

GET /protectedresource HTTP/1.1
  Host: resource.example.org
  Authorization: DPoP Kz~8mXK1EalYznwH-LC-1fBAo.4Ljp~zsPE_NeO.gxU
  DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6Ik
   ...

When the API receives both the DPoP proof and the Access Token it is now able to verify that the client that requested the Access Token is in possession of the same key material as the client that called the API.

Conclusion

I really like the approach this specification takes. The concept is easy to understand, and seems to be relatively simple to implement on the client and API with the capabilities found in modern browsers and programming frameworks.

In the next blog-post in this series I will describe how I implemented a proof-of-concept for the Authorization Server side using Duende IdentityServer.

WARNING: I don’t do a lot of programming, so the code examples are not for the faint of heart :)

Tilbake til notater