API authentication methods explained: what to use and when
API keys, OAuth, JWTs, mutual TLS, and signed requests each answer a different question. Picking by who is acting and what happens when the credential leaks.
On this page
- What we'll cover
- Two questions that decide it
- API keys
- OAuth, for acting on somebody's behalf
- Tokens the server does not look up
- Signed requests and mutual TLS
- Scoping, which matters more than the method
- Storage and rotation
- Authentication is not authorisation
- Authenticating with CodeWords
- What to get right first
- Frequently asked questions
- Related reading
Most comparisons of API authentication methods list them as though you were choosing a favourite. You are not. The method is largely determined by two things: whether the caller is acting as itself or on behalf of a person, and what the damage is when the credential leaks — because it will.
Once those two questions are answered, the choice is usually obvious, and the remaining work is the part nobody writes about: storing the credential, rotating it, and noticing when it is being used by something unexpected.
What we'll cover
- Two questions that decide it
- API keys
- OAuth, for acting on somebody's behalf
- Tokens the server does not look up
- Signed requests and mutual TLS
- Scoping, which matters more than the method
- Storage and rotation
- Authentication is not authorisation
- Authenticating with CodeWords
- What to get right first
- Frequently asked questions
Two questions that decide it
Who is acting? If your service is calling another service as itself, you want a machine credential — a key, a client credentials grant, or a certificate. If your application is acting for a specific person, on data that belongs to them, you want a delegated authorisation flow, which in practice means OAuth.
What happens when it leaks? Not if. A credential that grants everything, never expires, and is used by forty services is a credential whose compromise is unbounded. One that is narrowly scoped, short-lived, and attributable to one caller is a contained incident.
Those two answers determine the method. Everything else — which header, which grant type, which algorithm — is detail.
A third question is worth asking: can you rotate it without an outage? If the answer is no, that is the problem to fix first, whatever method you are using.
API keys
A long random string sent with each request, checked against a store.
The right choice for service-to-service calls where the calling service acts as itself, and it is far more often sufficient than its reputation suggests.
Simple is a feature. No flows, no refreshes, no clock skew, nothing to debug at two in the morning.
The weaknesses are real: they do not expire on their own, they are frequently over-scoped, and they end up in repositories, logs, and configuration files.
Send them in a header, never in a query string, because query strings are logged by servers, proxies, and browsers.
Give each caller its own key. A shared key means you cannot revoke one consumer, and you cannot tell which one is responsible for the traffic you are looking at.
OAuth, for acting on somebody's behalf
Its purpose is delegation: letting your application act on a user's data in another system without the user handing you their password.
The authorisation code flow is the one you want for a user-facing application, with PKCE, which is now the standard advice for public clients.
The client credentials grant is not delegation at all — it is a machine credential with extra steps, and it is a perfectly reasonable one when the provider offers it.
The refresh token is the long-lived secret, not the access token, and protecting it is where the security actually lives.
Refresh tokens frequently rotate, meaning each use issues a new one and invalidates the old, so the new value must be stored immediately or the connection is lost.
Most OAuth integration bugs are refresh bugs: a token stored in configuration the process cannot update, two runs refreshing at once, or a refresh token that expired during a quiet period.
Tokens the server does not look up
A signed token carries claims — who, what, and until when — with a signature the receiver verifies without a database lookup.
The appeal is avoiding a lookup per request, which matters at high volume and across service boundaries.
The cost is revocation. A token valid for an hour is valid for an hour, and there is no straightforward way to cancel it mid-life without reintroducing the lookup you were avoiding.
So keep lifetimes short and pair them with a refresh mechanism, which is the standard arrangement.
Verify properly. Check the signature, the issuer, the audience, and the expiry — skipping any of them is a real vulnerability rather than a shortcut.
Never trust the token's own algorithm field to decide how to verify it; decide that yourself from configuration.
Signed requests and mutual TLS
Request signing computes a signature over the method, path, headers, body, and a timestamp, using a shared secret. The secret never crosses the network.
It protects against replay and tampering as well as proving identity, which a bearer credential does not.
The cost is implementation complexity on both sides, and mismatched signature calculation is a frustrating class of bug.
Mutual TLS authenticates the client with a certificate at the connection level, which is strong and operationally heavy — certificates expire, and an expired client certificate is an outage.
Both suit high-value machine-to-machine traffic, particularly where a regulator or a counterparty requires them. For ordinary internal service calls they are usually more than the situation needs.
Scoping, which matters more than the method
A narrowly scoped key beats a broadly scoped certificate for limiting damage, and scope is the variable most often left at its default.
Read-only where reading is all you do. Most integrations are reporting, and most reporting credentials are granted write access because that was the easier option.
Per-resource where the API supports it, so a credential reaches the one project, repository, or dataset it needs.
One credential per consumer, so revocation and attribution are possible.
Never a personal account. A credential belonging to an individual breaks when they leave and carries everything they can do, which is invariably more than the integration needs.
Review scopes periodically. They only ever widen, because widening solves an immediate problem and narrowing solves a hypothetical one.
Storage and rotation
The unglamorous half, and the half that actually goes wrong.
A secret store, not configuration files, and certainly not the repository.
Environment variables are acceptable and are visible in process listings and crash dumps, which is worth knowing rather than worrying about excessively.
Make rotation possible without downtime: support two valid credentials at once, so the new one can be introduced before the old is removed.
Rotate on a schedule and confirm the next run succeeded, because a rotation that silently breaks a nightly job is discovered days later.
Rotate on departure. Anybody who had access to a credential still has it after they leave unless it changes.
Log authentication failures with enough detail to act, since a spike in them is either a rotation somebody forgot or something you want to know about.
Authentication is not authorisation
Two words that get used interchangeably and mean entirely different things, and conflating them is how systems end up with holes.
Authentication answers who is calling. Authorisation answers what they are allowed to do.
A valid credential is not permission. Every request needs both checks, and a system that verifies the token and then acts has performed only the first.
Check authorisation per resource, not per endpoint. An endpoint somebody may call does not mean every record it can return is theirs, and identifier-in-the-path access control failures are among the most common real vulnerabilities.
Scopes on a token are a coarse filter, not a substitute for checking that this caller may touch this specific object.
Act as the user where you can. An integration operating with its own broad privileges bypasses whatever access rules the system carefully enforces for people.
Log both. Knowing a request authenticated and was then refused is useful; a single "denied" with no distinction between the two tells you nothing about what to fix.
Authenticating with CodeWords
CodeWords connects to more than 3,000 integrations, and the connection is made once and reused rather than re-implemented per automation.
- Open CodeWords and start a new automation.
- Describe what should happen in plain language to Cody, the automation builder.
- Authorize the connection to each system, granting the narrowest access the workflow needs.
- Describe the exceptions: an expired credential, a revoked authorisation, a permission the automation turns out to lack.
You describe the outcome; Cody builds it, connects it, and deploys it. The free plan covers light use, with Pro at $39 per month and Business at $100 per month as usage grows; details are on the pricing page.
What to get right first
Scope, before anything about the method. A read-only credential limited to one resource contains its own compromise regardless of which authentication scheme carries it, and an over-scoped credential is dangerous no matter how sophisticated the scheme is.
Two habits make the difference. Give every consumer its own credential, since shared ones cannot be revoked or attributed and you will need both eventually. And make rotation possible before you need it — supporting two valid credentials at once is a small design decision that turns an outage into a routine change.
Frequently asked questions
Are API keys good enough?
For service-to-service calls where the caller acts as itself, usually yes, provided they are narrowly scoped, unique per consumer, sent in a header, and rotatable. Their bad reputation comes from how they are used rather than from what they are.
When do I actually need OAuth?
When your application acts on a specific person's data in another system. If your service is calling as itself, a machine credential is simpler and does the same job — and a client credentials grant is a machine credential with extra steps.
Why does my OAuth integration break after a few weeks?
Refresh handling, nearly always. Refresh tokens frequently rotate, so the new value must be stored immediately, in somewhere the process can write to. A refresh token can also expire if unused, which breaks monthly jobs after a quiet period.
Can a signed token be revoked?
Not straightforwardly, which is the trade for not needing a lookup. Keep lifetimes short and pair them with a refresh mechanism; anything else reintroduces the database check the design was avoiding.
Should credentials go in environment variables?
Better than a repository, less good than a secret store. They appear in process listings and crash dumps, which is worth knowing. The more important question is whether you can rotate them without an outage.
What is the most common mistake?
Over-scoping. Most integrations read, and most integration credentials can write, because write access was the default when somebody created it. Scope is also the thing nobody revisits, since widening solves a real problem and narrowing solves a hypothetical one.
How do I rotate without downtime?
Support two valid credentials simultaneously: introduce the new one, deploy, remove the old. Designing for this before you need it is what turns an urgent rotation into a routine change rather than a maintenance window.
Is a valid token enough to allow a request?
No. Authentication establishes who is calling; authorisation decides what they may do. A system that verifies the credential and then acts has skipped the second check, which is where most real access-control failures live.
Where should authorisation be checked?
Per resource, not per endpoint. Being allowed to call an endpoint does not mean every record it can return belongs to you, and identifier-in-the-path failures are among the most common vulnerabilities in practice.