Everyone who ever heard about OAuth2, possibly came across bearer tokens. Is it a special type of token? Not really. Bearer means one thing though. Whoever bears it (has in possession) is authorised to do stuff. Simples. If I steal your token, I can access the resource, same as if I steal your car key, I can drive away with your pride and joy, say an AMG Merc (even if it’s a Diesel…).
Protecting the Bear Er
My cute Bear Er requires protection, it’s valuable, it opens doors. That’s why all modern network traffic is encrypted in transit, as we need to present the bearer tokens to fulfil the authorisation requirements. If someone intercepts it, they can replay it and access will be granted. What if someone manages to steal it? Cookie theft if real and anything running client side is potentially vulnerable to theft.
There’s not much we can do about it or is there…?
Risk Based Authentication
RBA – the context of the request may come in handy and there’s a fair chance to stop the malicious attempt. Impossible travel, IP reputation, UEBA (User and Entity Behaviour Analytics) may help, but it’s not a sure thing. If you require a 100% method, you need to keep looking and you will find:
Proof Of Possession
Relatively new draft – you can read about it here. Demonstration of Proof-of-Possession, what a mouthful! It will take a while to implement in every OAuth2 protected flow, but we don’t need to wait to apply the logic.
The objective from the draft states:
The primary aim of DPoP is to prevent unauthorized or illegitimate parties from using leaked or stolen access tokens, by binding a token to a public key upon issuance and requiring that the client proves possession of the corresponding private key when using the token
The concept of PoP adds another layer of assurance between the client and resource server. The additional element is a cryptographical validation that the request came indeed from the party that requested the token in the first place.
We are seasoning our dish with a private and public keypair. The private key (as the name suggests) never leaves the client’s device. Let’s look at an example client credentials grant and how we can harden the utilisation of a token.
When the client requests a token it passes the public key formatted in a way authorisation server understands (there are a few standards JWT, JWE, JWK etc).
In my case I am going to use a simple JWK:
“jwk”:{
“kty”: “EC”,
“use”: “enc”,
“crv”: “P-384”,
“kid”: “pop-marcin”,
“x”: “dmoLQ02qYma5S3wVrSH7cA0e7dkl-K0hc4MB38TjT3XMCqxZUuXhIsFiZVFvCEUD”,
“y”: “5g-J7OQMlcxztF5NGDUhjIngwJROCB-fL4ThvHnQv-d5XJG28Vh7bnHK2uhRq6Jx”,
“alg”: “ECDH-ES”
}
I will add my base64 encoded JWK into the request as follows:
“cnf_key”: “ewogICJqd2siOnsKICAgICJrdHkiOiAiRUMiLAogICAgInVzZSI6ICJlbmMiLAogICAgImNydiI6ICJQLTM4NCIsCiAgICAia2lkIjogInBvcC1tYXJjaW4iLAogICAgIngiOiAiZG1vTFEwMnFZbWE1UzN3VnJTSDdjQTBlN2RrbC1LMGhjNE1CMzhUalQzWE1DcXhaVXVYaElzRmlaVkZ2Q0VVRCIsCiAgICAieSI6ICI1Zy1KN09RTWxjeHp0RjVOR0RVaGpJbmd3SlJPQ0ItZkw0VGh2SG5Rdi1kNVhKRzI4Vmg3Ym5ISzJ1aFJxNkp4IiwKICAgICJhbGciOiAiRUNESC1FUyIKICAgIH0KfQ==”
The authorisation server will return an access token, which will have the key embedded into the payload of the token. This is the binding bit that the standard talks about.
{
“sub”: “clientid1”,
“cts”: “OAUTH2_STATELESS_GRANT”,
“iss”: “https://some.authorization.server.com”,
“token_type”: “Bearer”,
“aud”: “clientid1”,
“grant_type”: “client_credentials”,
“scope”: [
“somescope”
],
“auth_time”: 1666095441,
“cnf”: {
“jwk”: {
“kty”: “EC”,
“use”: “enc”,
“crv”: “P-384”,
“kid”: “pop-marcin”,
“x”: “dmoLQ02qYma5S3wVrSH7cA0e7dkl-K0hc4MB38TjT3XMCqxZUuXhIsFiZVFvCEUD”,
“y”: “5g-J7OQMlcxztF5NGDUhjIngwJROCB-fL4ThvHnQv-d5XJG28Vh7bnHK2uhRq6Jx”,
“alg”: “ECDH-ES”
}
},
“exp”: 1666099041,
“iat”: 1666095441,
“expires_in”: 3600,
“jti”: “VcmCBzEkkiNdBFJuH2NTBy32SEQ”
}
Once the client requests a resource from the server, the server will not only validate the token as per usual, but also present a challenge to the requestor using the public key. That challenge can only be solved using the private key, which should only sit with the client itself.
Another way to fulfil this requirement is to sign each request with the private key, which will remove the need to send back a challenge, two-in-one, wash and go! You would need a timestamp or a unique identifier for each request and the only thing to worry about is… what if the request itself was intercepted and how do I prevent replay attack. But that’s a topic for another day 🙂