Skip to content
← All writing
Security9 Mar 20268 min read

OAuth protects your users. It does nothing for your services.

A valid token proves someone logged in. It doesn't prove the caller is who they say they are.

By Semir Hamid

Most internal architectures I've reviewed have good user authentication and essentially no service authentication. There's an OAuth flow at the edge, a JWT gets minted, and then that token is passed between a dozen internal services, every one of which trusts it completely.

That's fine right up until something inside the network can make HTTP calls. Then any component that can reach a service can call it, holding a token it was handed, and nothing in the system can tell the difference between the billing service and a compromised log shipper.

Two separate questions

  • Who is the user, and what are they allowed to do? That's the JWT's job and it does it well.
  • Which service is making this call, and is it allowed to? That's a different question and the JWT cannot answer it.

mTLS answers the second one

Every service gets a certificate. Every call presents it. The receiving service checks it against a CA it trusts and reads the identity out of the subject. Now 'the billing service called me' is a fact rather than an assumption, and you can write authorisation rules against it.

In Kubernetes this is mostly a configuration exercise — the mesh or the ingress will do the handshake for you and hand your app a header. The work is in certificate rotation, which you must automate on day one, because a manual rotation you do once a year is a manual rotation you will forget.

If an attacker inside your network has the same powers as your own services, you don't have a security boundary. You have a firewall and some optimism.

Worth saying

This is the control that auditors ask about and engineers skip, which is a shame, because it's one of the few security measures that also makes debugging easier. Knowing exactly which service called you is useful every single day.