Library
10 min read

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.

Your answer

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.

bash
curl https://api.stripe.com/v1/charges \
  -u sk_live_51H...xYz:     # Stripe sends the key as the basic auth username

It 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_ and pk_live_, GitHub's ghp_ and github_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.
Publishable keys are not a mistake

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.

1/6 A normal TLS handshake starts.

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.

Who is calling
The key is the account. Simple, long lived, and anyone who copies it is the merchant. Good for server to server developer APIs.

Side by side#

ChoiceWhat you gainWhat you payPick it when
API keyTrivial 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 tokenScoped, 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.
mTLSStrong 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.

Names you will hear
  • 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#

Interviewer
Design authentication for a public payments API.
The answer should be API keys, done carefully. Proposing mTLS for every merchant is a red flag for developer experience.
You
Merchants call from their servers, so secret API keys. I would prefix them, live versus test and secret versus publishable, so leaks are recognisable and test keys can never move money. I store only a hash, show the key once, and let merchants create restricted keys per integration and keep two live keys during rotation. I would register the prefix with GitHub secret scanning so leaked keys get revoked automatically. For outgoing webhooks I sign the body with HMAC and a timestamp so merchants can verify them and reject replays.
Every point limits the blast radius of the leak that will eventually happen.
Interviewer
Now a partner platform wants to manage payments on behalf of its merchants.
You
Then the data belongs to the merchant, not the partner, so OAuth. Each merchant authorises the partner with specific scopes, and can revoke it. That is what Stripe Connect does.
Which one fits?
Your backend calls the OpenAI API
A scheduling app reads users' Google Calendars
The orders service calls the payments service in your Kubernetes cluster
A mobile app embeds a key to call your backend

Checkpoint#

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?

Worth memorising
  • 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.
Say this in 60 seconds

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.