I did a lot of WS-Security in my (distant) past – and whenever we started looking into migrating to OAuth 2.0, there was this one thing on the security check-list that was missing in the OAuth world: proof of possession tokens (short PoP).
Now might be finally the time where they become real. But some history first.
OAuth 1.x had PoP tokens, but they were complicated to use and involved some non-trivial crypto. As part of the OAuth 2.0 “simplification”, proof-of-possession became optional and bearer tokens became the standard choice. This was actually one of the reasons why the main OAuth spec author left just a couple of months before the document was actually finished. See this video for background/entertainment.
One of his legacies though was that the OAuth 2.0 spec got split in two specs – OAuth 2.0 itself (RFC 6749) – and token usage (RFC 6750). The only token usage spec that was written by that time, was the “bearer token usage” with “proof-of-possession” to follow at some point. This never happened.
PoP tokens have some nice additional security properties, because they are bound to the client that requested the token in the first place. This means that a leaked/stolen token cannot be used by an attacker without knowing the additional key material that defines that binding. This key material was typically never sent over the wire, which makes network-based attacks much harder. This is especially nice for mobile applications, where the client frequently connects to untrusted networks or for non-repudiation.
In WS-Security, the token binding was done purely at the application level (or message level as they called it). A couple of attempts have been made to replicate that idea in OAuth as well (see here, here and here).
All of these approaches were doable (though not straightforward) but suffered from problems like e.g. not being able to deal with streamed data (btw – WS-Security had the exact same problem). In the end, the extra complexity was questionable and all that was left from that effort, was the standardization of a claim to express the binding. It’s called cnf – or confirmation. But nothing beyond that.
Having a PoP mechanism at the transport layer seemed to be the better alternative as it was content agnostic and required less code to be written at the application level. Enter token binding – well don’t, it’s a waste of time. This looked promising, but Google backed out at some point, which made this not feasible as a standard at the internet level.
As kind of a “last resort”, there has been the OAuth MTLS spec cooking for a while. It describes both using X.509 client certificates for OAuth 2 client authentication, and using the certificate for PoP (or sender-constraining as they call it). Both parts can be used independently, as we’ll see later.
When most people hear X.509, CAs and “Mutual TLS”, they shy away because this is very complicated technology and – yes – this is true. But on the other hand, MTLS has kind of a renaissance and is very often used these days in “microservice” scenarios where it’s less about the PKI aspect than providing key material.
The Mutual TLS spec describes two authentication modes for clients – one is a full blown CA/PKI style where you authenticate based on a trust root and distinguished names, the other one though is for self-signed certificates, where you get the similar benefits without running a complicated CA infrastructure.
In both cases, the client certificate can be used to bind the issued token to the client using the cnf claim.
Another interesting idea is using “ephemeral client certificates” – this means the client must not necessarily use the client certificate for authentication – this can be done in any way you like, e.g. private key JWTs or even shared secrets (though not recommended). The client cert’s only purpose is to provide some key material to the STS to do the token binding. This might be a gentle way to introduce PoP in your architecture.
To summarize: deploying client certificates is not necessarily as hard anymore as the last time you had a look it, and may be worth re-visiting.
What about SPAs?
Well – nothing really. MTLS is not really applicable, token binding is not happening. Other approaches have been tested, and while they work on a crypto level, they still can’t protected against the main attack vector in browsers, namely content injection attacks (aka XSS). So again – the extra effort is questionable.
In the SPA world, still the most secure option is to not store any tokens at all in the browser and only use them server-side. Feel free to layer PoP on top of that using the above technique.
What does this mean for .NET and IdentityServer Users?
IdentityServer4 had support for the MTLS spec since March 2019. We just recently extended that support for the upcoming version 4 for allowing more flexible hosting of the MTLS endpoints, e.g. on a sub-path, on sub-domains or on completely different domains. We also added support for adding the cnf claim also for ephemeral client certificates (.NET Core has an API to create X.509 certs on the fly which works really nice with that approach).