API keys vs OAuth tokens vs mTLS
Published
Every request to an API has to answer "who is calling?". The trick is that "who" means different things. Sometimes it is a project: "this is Zomato's backend, bill Zomato". Sometimes it is a person, through an app: "this is Asha, using the Notion app, and she allowed it to read her calendar". Sometimes it is a machine: "this is pod 7 of the payments service, and nobody else can pretend to be it". Three kinds of "who", three tools.
You are designing the public API for a payments company like Razorpay or Stripe. Merchants call it from their servers. Would you use API keys, OAuth or mTLS, and what would you do to limit the damage when a merchant leaks their key on GitHub?
API keys: a password for a project#
An API key is a long random string the provider gives you once. You send it with every request, usually in a header, and the provider looks it up to find your account.
curl https://api.stripe.com/v1/charges \
-u sk_live_51H...xYz: # Stripe sends the key as the basic auth usernameIt is the simplest thing that works, which is why most developer APIs start here: Stripe, OpenAI, Google Maps, Twilio. A key identifies a project or account, not a person. It does not expire on its own, and anyone who copies it is you.
The good providers make keys safer with a few habits worth copying:
- Prefixes that say what the key is. Stripe's
sk_live_,sk_test_andpk_live_, GitHub'sghp_andgithub_pat_. You can tell at a glance whether it is secret, and scanners can recognise it. GitHub's secret scanning watches public commits for dozens of providers' prefixes and tells the provider, who revokes the key, often within minutes. - Hash the key at rest, like a password. If your database leaks, the keys do not. Show the full key exactly once, at creation.
- Restricted keys with only the permissions a job needs, and an IP allowlist.
- Two active keys at once, so a customer can roll to a new one without downtime.
Stripe's pk_live_ key is meant to sit in your web page. It can only do harmless things like
create a card token. The secret sk_live_ key never leaves your server. If a key must work
from a browser or mobile app, it is public by definition, so design it to be harmless.
OAuth tokens: a person, through an app#
When a third party app acts on behalf of a user, an API key is the wrong shape. The key would give the app access to everything in the account, forever, and the user has no say. OAuth asks the user for consent to specific scopes, and gives the app a short lived token tied to that user, those scopes and that app. The user can revoke it from their settings page without changing their password. (The flows are in OAuth 2.0 flows.)
GitHub has both: a personal access token is effectively an API key for yourself, while a GitHub App or OAuth app gets tokens scoped to the repositories a user or organisation granted. Rule of thumb: if the data belongs to someone other than the developer calling the API, use OAuth.
mTLS: a machine, by certificate#
In normal TLS, only the server proves who it is, with a certificate. Your browser checks
that hdfcbank.com really is HDFC Bank. In mutual TLS, the client also presents a
certificate, and the server checks it against a certificate authority it trusts. Nothing is
sent that can be copied out of a header: the proof is possession of a private key that never
leaves the machine.
The hard part of mTLS is not the handshake. It is issuing and rotating certificates for thousands of short lived containers. That is what a service mesh does: Istio or Linkerd put a sidecar proxy beside every pod, give each workload a certificate (often in the SPIFFE format) that lives for hours, rotate it automatically, and do mTLS between sidecars so your application code never sees it. Banks also use mTLS for partner APIs, and open banking standards in the UK and elsewhere require it.
Side by side#
| Choice | What you gain | What you pay | Pick it when |
|---|---|---|---|
| API key | Trivial for developers: one header. Easy to issue, revoke and meter. | Long lived bearer secret. Leaks through repos, logs and screenshots. Identifies an account, not a person. | Developer facing APIs called from servers: payments, SMS, maps, LLMs. |
| OAuth access token | Scoped, short lived, tied to a user who consented and can revoke. | Redirects, consent screens, token refresh. Real work for API consumers. | Third party apps acting on a user's data. Your own apps calling your APIs. |
| mTLS | Strong machine identity. Nothing copyable travels with the request. | Certificate issuance and rotation, painful without a mesh or platform. | Service to service inside your network, bank and partner integrations. |
These combine. A common pattern: mTLS between services for which workload is calling, plus a JWT forwarded along for which user the request is for. And for webhooks you send to customers, the usual tool is a fourth one, an HMAC signature over the body with a shared secret, so the receiver can check the request really came from you.
- StripePrefixed secret and publishable keys, restricted keys
- GitHubSecret scanning, PATs, GitHub Apps with OAuth
- IstioService mesh, automatic mTLS between pods
- AWSSigV4: every request signed with HMAC
- KongGateway plugins for keys, OAuth and mTLS
- Let's EncryptFree server certificates, not client certs
In the interview#
Checkpoint#
1. Why should an API provider store a hash of each key rather than the key?
2. When is an API key the wrong choice and OAuth the right one?
3. What does mTLS prove that a bearer token cannot?
- API key = a project. OAuth token = a user through an app. mTLS = a machine.
- Prefix keys, hash them at rest, show once, allow restricted and overlapping keys.
- If the data belongs to someone else, use OAuth.
- mTLS is easy to describe and hard to operate. A service mesh rotates the certificates.
- Webhooks: sign with HMAC plus a timestamp.
Three tools answer who is calling, for three kinds of who. An API key is a long random secret that identifies a project or account; it is simple, so developer APIs like Stripe and OpenAI use it, but it is a long lived bearer secret, so I prefix keys to make leaks recognisable, hash them at rest, show them once, support restricted keys and two keys during rotation, and plug into secret scanning. An OAuth token represents a user acting through an app with scopes they consented to, short lived and revocable; it is the right choice whenever the data belongs to someone other than the developer. mTLS proves a machine's identity by possession of a private key in a mutual TLS handshake; it suits service to service traffic, usually with a service mesh issuing and rotating short lived certificates. They combine: mTLS for the workload, a JWT for the user, HMAC signatures for webhooks.