The Web API Authentication guide, TLS Client Certificates

This post is part of a multi-part series. It builds on the first post, where I describe the framework we will use to evaluate authentication schemes. If you have not, it is probably a good idea to read it now (hint).

Here is where we are.

II. Evaluation of standard authentication schemes

TLS client certificates

CC0 image by Tumisu

Every time you visit a web page using HTTPS, you are taking advantage of something called TLS. It's the protocol under HTTP, which provides confidentiality, integrity for the connection and some authenticity for the domain you are connecting to. Okay, so what does this have to do with authentication?

Well, the TLS protocol authenticates the website you are connecting to, and it relies on the Public Key Infrastructure to do so. The idea is rather simple. Trusted third parties, also known as CAs, issue digital certificates to domain owners. The end-users, or more precisely end-browsers, merely trust the root certificate of the CAs and build a trust-chain between the domain's certificate and the root of a CA. This setup, in its current form, is only used to verify the website to the user. But wait, what if the same concept could be used to authenticate users to your site?

As it turns out, this is precisely what TLS client certificates do.

How it works?

This scheme is a bit different from traditional auth schemes because authentication does not happen over HTTP. It all plays out a level lower, in the TLS protocol. In fact, the whole TLS handshake occurs before any HTTP request is sent over the wire.

The whole process starts out with a handshake. It involves lots of complicated crypto operations and key-exchange protocols. What's essential for us is, that digital certificates are exchanged, and both parties validate these before the connection is established. First, we'll take a look at what happens client side, in the browser, and then dive into how this could work for the server.

Once a client receives the server certificate, it does quite a few checks. Here is the list.

  • Is this certificate issued for this domain?
  • Is the certificate in its validity period?
    • Current time is after the issued date.
    • Current time is before the certificate expiry date.
  • Can a chain be built back to a trusted root certificate?
  • Is the certificate still active according to its issuer?
    • It has not been revoked.

The certificate is only accepted if it passes all the above checks.

Now, let's flip this around and check out how this would work on server side.

Most of the above checks are the same on the server side. The only difference is the domain test. During client authentication, this does not make sense, as there is nothing to compare the issued party to. If everything else checks out, the server will accept the identity in the certificate as the authenticated user.

Now, you may have noticed a missing piece. Certificates you see on the web are issued by trusted CAs. Website owners visit a CA's page and, after a quick ownership validation, receive a new certificate for their domain. So, how does this work for client certificates? Well, as always, it depends.

For users, the client certificates can either be purchased from a CA or be issued by the website it is used for upon registration. Either way, the user will end up with a certificate he needs to keep safe and be able to present upon authentication.

For backend authentication, these certificates are usually issued by a private CA, operated by the company using them. A great example of this is Cloudflare's Origin CA used on their internal networks.

If this all sounds far-fetched, I am with you. You do not see TLS client authentication in the wild that often. In fact, I have been looking for a useful sample application to link to, and I could not find one.

I built a TLS Client Auth sample app!

It's a minimal Docker container running Apache and PHP. It's configured to use TLS Client Authentication. The repository is on Github, go ahead and check it out. Just clone it and follow the instructions.

GitHub

Complexity

Mutual TLS authentication may seem very simple at first. And it definitely can be, if you are not the one handling the configuration. Otherwise, there are a few key things you need to take on.

First, you must have a solution in place to issue certificates to your clients. You can either trust public CAs and leave all this to your clients or set up your own private PKI. The latter is harder than it looks. You need to solve the issuance, distribution, revocation, and the renewal of certificates. Neither of which is trivial.

Second, to be able to use TLS for client authentication you need to be able to configure the application which terminates the TLS connection. In the simplest case, this may be your local Apache. In more complex scenarios, it is probably the platform provided load balancer, a reverse proxy or anything that might stand in front of your app. This constraint pretty much rules out any out-of-the-box PaaS solutions, since you usually cannot configure such components.

Third, you must make sure your clients (browsers, libraries) support TLS mutual authentication. Browsers are doing pretty well regarding compatibility, and most mainstream libraries should be okay as well.

Given the above, TLS mutual authentication is considered quite complicated.

Reliance on HTTPS

Well, this is a no-brainer. TLS client authentication doubles down on HTTPS and therefore relies upon it completely. There is more though.

Using mutual authentication provides some protection even if the attacker has active MITM capabilities. It is possible to fool the client into talking to a fake server by injecting a phony root certificate into the client's trust store. However, it is nearly impossible to deceive the server to speak to a client who does not possess the necessary private key, even if the attacker can modify traffic at will.

CSRF protection

This is a problem you need to solve manually. TLS authentication is completely handled by the browser. Therefore any connection going towards the target domain will use the establish and authenticated TLS session.

Between servers, this is not a concern.

Replay and integrity protection

Everything is handled by TLS, including integrity and replay protection. Regarding these two cases, mutual authentication does not guarantee anything over a regular TLS connection.

Recommended use cases

CC0 image by 6069386

You are capable of using this scheme on frontend and backend. Frontend usage makes for nice user experience, if used from a single device, but has major drawbacks. As the browser remembers the certificate used to authenticate for the site you need to implement CSRF protection. This behavior also makes it impossible to log out of a session other than clearing all kinds of caches.

Furthermore, your user must install the certificate on all devices she would like to use with your services. This significantly degrades the otherwise good UX. Here is an article on why TLS mutual authentication is not recommended for frontend usage.

On the backend, this is a significant asset. If you have access to the infrastructure, i.e., not using a PaaS, you will likely find TLS mutual authentication a potent tool. Most web servers support this scheme out of the box. Major companies like Cloudflare also use this scheme between their backend services. Also, Google's ALTS is conceptually based on TLS client certificates.

Long story short, I recommend using this scheme on the backend when you have elevated security requirements.

Taking care of your auth scheme

First things first, before committing to this scheme make sure your platform and all your clients support it.

Next, whenever you use HTTPS, it is a good idea to harden your settings. SSLLabs has a great guide.

If you are issuing certificates, you need to create a well-defined process and infrastructure for distribution, revocation, and renewal. This may seem trivial at first, but trust me, it's not. A proper process can save you significant pain.

You also need to pay attention to what kind of certificates you issue. Prefer modern ECC based ones over RSA. Also, keep in mind the key size: 256 bits for ECC and 2048 bits for RSA should cover most of your use-cases. Furthermore, think about the certificate's lifespan. Shorter lived certificates offer better security, but they come at the price of having to renew often.

Coming up next

If you got this far, you should have a pretty good idea about your options regarding web API authentication. This was the last scheme I discussed, and the next post is all about putting everything together. I'll give you a cheat sheet and add some closing thoughts. Stay tuned!

Make sure to subscribe to the newsletter (upper right corner of the page), so you won't miss it.

Spot a mistake in reasoning? Have a different opinion? Sound off in the comments!