API & Automation·7 min read·

API Authentication Options Ranked: Keys, Tokens, mTLS

Compare API keys, bearer tokens, and mTLS to pick the right authentication method for your machine-to-machine API clients and workloads of every kind.

NB

Netbay Engineering

Netbay Engineering

On this page

Every public API eventually faces the same question: how do callers prove who they are? The answer shapes your threat model, your margin against abuse, and how much work you push onto clients. This post ranks the three mainstream options for machine-to-machine authentication — API keys, signed bearer tokens, and mutual TLS — and gives you a decision rule for choosing among them.

The three candidates in one view

  • **API keys** are opaque shared secrets issued by your control panel. Simple to understand, simple to rotate, but they travel as a static string on every request.
  • **Bearer tokens** (often JWTs) are cryptographically signed payloads that encode identity and expiry. Stateless servers, no database lookup per call, but revocation is harder.
  • **mTLS** requires both sides to present X.509 certificates at the TLS layer. The strongest channel binding, but certificate issuance and lifecycle management are the heaviest operational burden.

The diagram below shows how each method binds the caller to the request.

API key static string, header Bearer JWT signed, expires mTLS cert channel bound API server validate per method trust level rises

Ranking by trust and operational cost

For pure server-to-server integration where a human keys in a secret once, **API keys win** on simplicity. You issue an 'X-API-Key: sk_live_...' header, hash it server-side, and you are done. The downside is that a leaked key is a capable pass for anyone who copies it, so it should always be scoped to narrow permissions and rotated on a schedule.

**Batch/stateless server-to-server** workloads with occasional calls benefit from short-lived bearer tokens. An OIDC flow or your own signing service issues a token that the API verifies without touching a database. Because the token self-describes expiry, you avoid a session lookup on every request — the main reason large B2B platforms prefer them.

**mTLS ranks highest** when both parties are fully controlled or when compliance demands strong channel binding. Because the identity claim rides inside the TLS handshake, there is no header to steal and replay anywhere else. It is overkill for broad public consumer APIs but ideal for internal service meshes or regulated partners.

A concrete rank for typical teams

  1. Public consumer or partner API with moderate abuse risk: API keys (with per-key scopes and rate limits).
  2. High-volume automated clients you trust but want stateless checks: short-lived JWTs.
  3. Regulated, high-value, or fully controlled peers: mTLS.
  4. Hybrid: use API keys at the edge and mint short JWTs internally.

Deciding in code

The principle behind every choice is that your credential should be as weak as the least-restrictive deployment allows, and as strong as the value behind it demands. An internal pipeline that only reads metrics can happily use a simple API key; an endpoint that triggers payments should insist on mTLS or short-lived signed tokens, because the cost of a single forged request is far higher than the operational overhead.

Here is a minimal Go-style sketch showing the three checks side by side. Note that the key check is a constant-time compare to resist timing attacks.

go
func authenticate(r *http.Request, cfg Config) (string, error) {
    switch cfg.Method {
    case "apikey":
        got := r.Header.Get("X-API-Key")
        if subtle.ConstantTimeCompare([]byte(got), []byte(cfg.Key)) != 1 {
            return "", errors.New("invalid key")
        }
        return "client-a", nil
    case "bearer":
        raw := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
        tok, err := parseAndVerify(raw, cfg.SigningKey)
        if err != nil { return "", err }
        return tok.Subject, nil
    case "mtls":
        cert := r.TLS.PeerCertificates
        if len(cert) == 0 { return "", errors.New("no client cert") }
        return cert[0].Subject.CommonName, nil
    }
    return "", errors.New("unsupported method")
}

The second example shows how to mint a short-lived bearer token server-side, keeping the signing key out of the payload path.

javascript
const crypto = require("crypto");
const header = Buffer.from(JSON.stringify({ alg: "HS256", typ: "JWT" }))
  .toString("base64url");
const payload = Buffer.from(JSON.stringify({
  sub: "client-a",
  iat: Math.floor(Date.now() / 1000),
  exp: Math.floor(Date.now() / 1000) + 900,
})).toString("base64url");
const sig = crypto.createHmac("sha256", process.env.SIGN_KEY)
  .update(header + "." + payload).digest("base64url");
console.log(header + "." + payload + "." + sig);

Takeaway

Rank by threat model, not fashion: API keys for simplicity, JWTs for stateless scale, mTLS for high-value peers. Whichever you choose, scope keys narrowly and rotate them. If you want a fast place to run these patterns against your own endpoint, spin up an Ubuntu 24.04 VPS on Netbay from DC01 in Lucknow in under 60 seconds — netbayhosts.in.

Keep reading

Follow along on a real VPS

Deploy Linux in under 60 seconds

These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.

Deploy an instance