UseToolSuite UseToolSuite

API Authentication Methods Compared: API Keys, OAuth 2.0, JWT, HMAC & mTLS

A practical engineering guide to API authentication. Understand API keys, Basic auth, Bearer tokens, OAuth 2.0, JWT, HMAC request signing, and mutual TLS — and when to use each.

Necmeddin Cunedioglu Necmeddin Cunedioglu 12 min read
Part of the Web Security: Encoding and Hashing Guide series

Practice what you learn

JWT Token Builder

Try it free →

API Authentication Methods Compared: API Keys, OAuth 2.0, JWT, HMAC & mTLS

“How should we authenticate our API?” is one of the most consequential — and most muddled — questions in backend engineering. The confusion is understandable: the landscape mixes token formats (JWT), authorization frameworks (OAuth 2.0), credential types (API keys), signing schemes (HMAC), and transport-layer mechanisms (mTLS), and they’re often discussed as if they were interchangeable options on a single menu. They aren’t. They operate at different layers and solve different problems, and the right answer depends entirely on the relationship between caller and API.

This guide untangles them. We’ll define authentication versus authorization, walk through each major method — what it is, how it works, and when it fits — and finish with a decision framework and a set of universal rules that apply no matter which method you choose. The goal is that by the end you can look at any API integration and confidently say “this calls for X, because Y.”

First, a crucial distinction: authentication vs authorization

These two words are constantly conflated, and getting them straight clarifies everything that follows.

  • Authentication (AuthN) answers “who are you?” It verifies identity — by a password, a token, a key, a certificate.
  • Authorization (AuthZ) answers “what are you allowed to do?” It checks permissions for an already-authenticated identity.

The flow is always authenticate first, then authorize: the API verifies who is calling, then checks whether that caller may perform this specific action (often via scopes or roles). Most of the methods below are primarily about authentication; authorization is layered on top. Keep the two separate in your design and your security reasoning gets much cleaner.

The methods, from simplest to strongest

API keys

An API key is a long, random string that identifies and authenticates the application making a request — not a human user. The client sends it, usually in a header (X-API-Key: … or Authorization: Bearer …), and the server looks it up.

Strengths: dead simple to implement and use, perfect for server-to-server calls and developer integrations where you just need to know which application is calling.

Weaknesses: it’s a static, long-lived secret. If it leaks, anyone holding it is the application until you rotate it. API keys identify an app, not an end user, and carry no built-in scoping or expiry unless you add it.

Use when: identifying an application (a backend service, a script, a partner integration) rather than an end user, and the caller can keep the key secret (i.e., server-side). Never put an API key in client-side JavaScript or a mobile app binary, where it can be extracted.

HTTP Basic authentication

Basic auth sends username:password, Base64-encoded, in the Authorization header. It’s trivial and universally supported — and it’s only acceptable over HTTPS, because Base64 is encoding, not encryption: anyone who intercepts the header decodes the credentials instantly. Basic auth is fine for simple internal tools and quick integrations behind TLS, but it sends reusable credentials on every request, so it’s a poor fit for anything user-facing or high-value.

Bearer tokens

A Bearer token is any token sent as Authorization: Bearer <token> — “whoever bears this token is granted access.” The token might be an opaque random string (validated by lookup) or a JWT (validated by signature). Bearer tokens are the workhorse of modern APIs, almost always issued by an OAuth 2.0 flow. The defining property: possession is sufficient. That makes secure storage and short lifetimes essential — a stolen Bearer token is a stolen session until it expires.

OAuth 2.0

OAuth 2.0 is not a single credential but an authorization framework for delegated access. It solves a specific, important problem: how can a user let a third-party application access some of their data on another service without handing over their password?

When you click “Allow App X to access your Google Calendar,” OAuth is what runs. The user authenticates with the service they trust (Google), consents to a scoped grant (“read calendar, nothing else”), and the application receives an access token — typically a short-lived Bearer token (often a JWT) — that it uses to call the API. A refresh token lets it obtain new access tokens without re-prompting the user.

Strengths: delegated, scoped, revocable, user-consented access; the user’s password is never shared with the third party; tokens are short-lived and limited in scope.

Weaknesses: complexity. OAuth has multiple flows (Authorization Code with PKCE for apps, Client Credentials for server-to-server), and misimplementing it is a real risk. Use a reputable library or identity provider rather than hand-rolling it.

Use when: users grant third-party apps access to their data, or you need standardized, scoped, revocable access for a user-facing API. OAuth 2.0 (with OpenID Connect for the identity layer) is the industry standard here.

JWT (the token, not the method)

JWT deserves its own section precisely because it is so often misclassified. A JWT is a token format, not an authentication method. It’s three Base64URL-encoded parts — header, payload (claims like sub, exp, scope), and signature — joined by dots. The signature (HMAC or RSA/ECDSA) lets a recipient verify the token wasn’t tampered with without a database lookup, which is why JWTs enable stateless authentication: the API trusts a valid signature instead of checking a session store.

Two things engineers must internalize:

  1. The payload is encoded, not encrypted. Anyone can decode a JWT and read its claims. Never put secrets in a JWT. The signature prevents tampering, not reading. Inspect any token with a JWT decoder to see this for yourself.
  2. Statelessness cuts both ways. Because the API doesn’t check a store, you can’t easily revoke a JWT before it expires. The mitigation is short lifetimes (minutes) plus a refresh-token mechanism, or a token denylist for emergencies.

JWTs commonly serve as the OAuth access token. They’re a mechanism you compose with a real auth strategy — generate signed test tokens with a JWT builder and validate the signing with an HMAC generator.

A note on JWT security pitfalls

JWTs have two infamous attack classes worth guarding against. The alg=none attack: an attacker sets the algorithm header to “none,” strips the signature, and a naive verifier that trusts the header accepts the forged token. The algorithm-confusion attack: a server expecting RS256 (asymmetric) is tricked into verifying an HS256 token using the public key as the HMAC secret. The defense for both is the same: never let the token dictate the algorithm. Configure your verifier with an explicit allowlist of accepted algorithms and reject none.

HMAC request signing

HMAC signing proves both authenticity (the request came from someone holding a shared secret) and integrity (it wasn’t altered in transit). The client builds a canonical string from the request — method, path, key headers, a timestamp, and a hash of the body — computes an HMAC over it with the shared secret, and sends the result as a signature header. The server reconstructs the same canonical string and recomputes the HMAC; if they match, the request is genuine.

Strengths: the secret itself is never transmitted (only a derived signature), the request can’t be tampered with undetected, and a timestamp prevents replay attacks. This is why webhook providers (GitHub, Stripe, Shopify) sign their payloads with HMAC and why AWS Signature v4 signs API requests.

Weaknesses: more complex to implement than a Bearer token, and both sides must canonicalize the request byte-for-byte identically — the number-one source of “signature mismatch” bugs.

Use when: verifying webhooks, or securing server-to-server APIs where both parties can hold a shared secret and you want integrity and replay protection. Critically, verify signatures in constant time (crypto.timingSafeEqual, hmac.compare_digest) to avoid timing attacks, and verify against the raw request body, since re-serializing JSON changes the bytes and breaks the signature.

Mutual TLS (mTLS)

Ordinary HTTPS authenticates only the server to the client. mTLS makes it mutual: the client also presents a certificate, and the server validates it. Neither side trusts the connection until both certificates check out. This pushes authentication down to the transport layer, before any application code runs.

Strengths: very strong, certificate-based mutual authentication; ideal for zero-trust internal architectures, service meshes, and high-security B2B APIs where you control both ends and can manage certificates.

Weaknesses: operational overhead — issuing, distributing, rotating, and revoking certificates at scale is non-trivial, which is why mTLS is most common inside controlled environments (often automated by a service mesh) rather than on public, consumer-facing APIs.

Putting it together: a decision framework

The methods aren’t competitors on one axis; they fit different relationships. This table maps the decision:

ScenarioRecommended method
Identify a calling application (server-to-server)API key or HMAC
User grants a third-party app access to their dataOAuth 2.0 (+ OpenID Connect)
User-facing API needing stateless, scoped accessOAuth 2.0 with short-lived JWT access tokens
Verifying webhooks from a providerHMAC signature verification
Server-to-server with integrity + replay protectionHMAC request signing (e.g. AWS SigV4 style)
Sensitive internal microservices, zero-trustmTLS
Quick internal tool behind TLSBasic auth (HTTPS only)

A representative production stack combines several layers: OAuth 2.0 issuing short-lived JWT access tokens for the public, user-facing API; API keys or HMAC for partner and server-to-server integrations; and mTLS between sensitive internal services. There is rarely one method for an entire system — you match each to its relationship.

Universal rules, regardless of method

No matter what you choose, these apply:

  1. Always use HTTPS/TLS. Every method above is broken over plain HTTP — credentials, tokens, and signatures can all be intercepted. There are no exceptions.
  2. Never put secrets in client-side code or URLs. API keys in JavaScript bundles or mobile binaries can be extracted; tokens in URLs leak into logs, browser history, and Referer headers. Secrets belong server-side; tokens belong in headers (or httpOnly cookies).
  3. Scope and expire aggressively. Grant the minimum permissions and the shortest practical lifetime. Short-lived access tokens plus refresh tokens limit the blast radius of a leak.
  4. Verify signatures in constant time. A naive == on signatures leaks timing information that can be exploited to forge them.
  5. Make revocation possible. Have a path to invalidate a credential — key rotation, token denylists, certificate revocation — before you need it during an incident.
  6. Don’t roll your own. Use vetted libraries and identity providers for OAuth, JWT validation, and TLS. The subtle failures (algorithm confusion, timing leaks, canonicalization bugs) are exactly what well-tested libraries handle.

Token storage, rotation, and revocation in depth

Choosing a method is the start; the lifecycle of the credentials it issues is where security is often won or lost. Three operational concerns deserve real attention.

Storage. Where a token lives determines how easily it’s stolen. In browsers, the choice is between localStorage and cookies, and it’s a genuine trade-off. Storing a token in localStorage exposes it to any cross-site scripting (XSS) on the page — a single injected script can read every token. Storing it in an httpOnly cookie keeps it out of JavaScript’s reach (mitigating XSS theft) but introduces cross-site request forgery (CSRF) risk, which you counter with the SameSite cookie attribute and anti-CSRF tokens. The common production pattern is a short-lived access token plus a longer-lived refresh token, with the refresh token in an httpOnly, Secure, SameSite=Strict cookie. On servers and in mobile apps, secrets belong in a secrets manager or the platform keystore — never in source control, never in a config file committed to the repo.

Rotation. Long-lived static credentials are a liability because a single leak compromises you until you notice. Short lifetimes shrink that window. Access tokens measured in minutes, refreshed silently via a refresh token, mean a stolen access token is useless within the hour. API keys and signing secrets should be rotatable on a schedule and immediately on suspicion of compromise — which requires supporting multiple valid credentials at once during a transition window, so you can introduce the new one before retiring the old without an outage.

Revocation. This is JWT’s well-known weakness and a reason the method choice matters. Because a stateless JWT is trusted on its signature alone, you can’t simply delete it server-side to log a user out before it expires. The mitigations: keep access-token lifetimes short so revocation is rarely urgent; maintain a refresh-token store you can invalidate (revoking the refresh token stops new access tokens); and for emergencies, keep a denylist of token identifiers (jti) that your verifier checks. Opaque tokens validated by lookup don’t have this problem — you delete the server-side record and the token is instantly dead — which is a legitimate reason to prefer them when immediate revocation is a hard requirement. Whichever you choose, have a working revocation path before you need it, not improvised during an incident.

Centralizing auth: gateways and identity providers

As a system grows past a handful of services, implementing authentication separately in each one becomes both repetitive and dangerous — every service is a chance to get it subtly wrong. The mature pattern is to centralize.

An API gateway sits in front of your services and handles authentication once, at the edge: it validates the token (or key, or certificate), rejects unauthenticated requests before they reach your code, and forwards verified identity to the services behind it (often as a trusted internal header or a re-signed internal token). This means your individual services don’t each reimplement JWT validation or OAuth flows — they trust the gateway’s verdict. It also gives you a single place to enforce rate limiting, logging, and policy.

An identity provider (IdP) — whether a hosted service or a self-run one — owns the user-authentication and token-issuing logic so your applications don’t. Standards like OAuth 2.0 and OpenID Connect exist precisely so you can delegate the hard, security-critical parts of authentication to a dedicated, audited system rather than building login, consent, token issuance, and key management yourself. The consistent advice across this entire guide — don’t roll your own — reaches its conclusion here: the more of authentication you can delegate to battle-tested gateways and identity providers, the fewer chances you have to introduce the subtle, catastrophic bugs that hand-built auth is famous for.

Testing your API auth

Before shipping, exercise your authentication paths end to end. Reproduce a request the way a client would with an API Request Builder, inspect the tokens you issue with a JWT decoder, and confirm your HMAC signatures match what your verifier expects with an HMAC generator. The most common production auth bug isn’t a broken algorithm — it’s a mismatch between what the client sends and what the server expects (a missing Bearer prefix, a re-serialized body that breaks a signature, a token verified with the wrong algorithm). Test the exact bytes.

Conclusion

API authentication has no universal “best” answer because the methods solve different problems at different layers. API keys identify applications. OAuth 2.0 delegates scoped, revocable user access. JWT is a token format — often the access token — not a method in itself. HMAC signing proves authenticity and integrity between parties sharing a secret, securing webhooks and server-to-server calls. mTLS delivers certificate-based mutual authentication for zero-trust internals. Choose by the relationship: who’s calling, what they can keep secret, and how much trust exists. Then apply the universal rules — HTTPS everywhere, secrets server-side, tight scopes and short lifetimes, constant-time verification, and proven libraries over hand-rolled crypto. Audit your current API: for each integration, can you name the method and the reason? If not, that’s the place to start.

Necmeddin Cunedioglu
Necmeddin Cunedioglu Author
12 min read
-- views

Software developer and the creator of UseToolSuite. I write about the tools and techniques I use daily as a developer — practical guides based on real experience, not theory.