Skip to main content

API Authentication Guide: JWT, OAuth 2.0, API Keys & mTLS

Choosing the right authentication mechanism for your API depends on who the caller is, where tokens are stored, and what security guarantees you need. This guide walks through every major strategy with implementation examples and a comparison table.

✓ API keys, JWT & OAuth 2.0 ✓ Refresh token rotation ✓ mTLS for service-to-service ✓ Auth strategy comparison table

JWT: Structure, Validation & Refresh Tokens

A JWT consists of three base64url-encoded parts: header (algorithm), payload (claims), and signature. Validate the signature and claims on every request:

import jwt from 'jsonwebtoken';
import { Request, Response, NextFunction } from 'express';

// Issue tokens — short-lived access + long-lived refresh
function issueTokens(userId: string, roles: string[]) {
  const accessToken = jwt.sign(
    { sub: userId, roles, type: 'access' },
    process.env.JWT_SECRET!,
    { expiresIn: '15m', issuer: 'devplaybook.cc', audience: 'api' }
  );
  const refreshToken = jwt.sign(
    { sub: userId, jti: crypto.randomUUID(), type: 'refresh' },
    process.env.REFRESH_SECRET!,
    { expiresIn: '7d' }
  );
  return { accessToken, refreshToken };
}

// Middleware — validates Bearer token on every protected request
export function requireAuth(req: Request, res: Response, next: NextFunction) {
  const token = req.headers.authorization?.replace('Bearer ', '');
  if (!token) return res.status(401).json({ error: 'Missing token' });

  try {
    const payload = jwt.verify(token, process.env.JWT_SECRET!, {
      issuer: 'devplaybook.cc',
      audience: 'api',
    }) as any;

    if (payload.type !== 'access') throw new Error('Wrong token type');
    req.user = { id: payload.sub, roles: payload.roles };
    next();
  } catch (err) {
    res.status(401).json({ error: 'Invalid or expired token' });
  }
}

OAuth 2.0 Client Credentials Flow (M2M)

For machine-to-machine authentication (background jobs, microservices), use the OAuth 2.0 Client Credentials grant — no user involved:

# Step 1: Client requests access token using client_id + client_secret
POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id=svc-orders
&client_secret=cs_live_abc123
&scope=users:read orders:write

# Step 2: Authorization server responds with short-lived access token
{
  "access_token": "eyJhbGci...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "users:read orders:write"
}

# Step 3: Service uses the token as Bearer in API calls
GET /api/v1/users
Authorization: Bearer eyJhbGci...

# Client implementation — cache token, refresh before expiry
class OAuth2Client {
  private token: string | null = null;
  private expiresAt = 0;

  async getToken(): Promise<string> {
    if (this.token && Date.now() < this.expiresAt - 30_000) return this.token;
    const res = await fetch(process.env.TOKEN_URL!, {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({ grant_type: 'client_credentials',
        client_id: process.env.CLIENT_ID!, client_secret: process.env.CLIENT_SECRET!,
        scope: 'users:read' }),
    });
    const data = await res.json();
    this.token = data.access_token;
    this.expiresAt = Date.now() + data.expires_in * 1000;
    return this.token!;
  }
}

mTLS: Mutual TLS for Service-to-Service

mTLS requires both client and server to present certificates. Zero tokens to manage — the TLS handshake IS the authentication:

import https from 'https';
import fs from 'fs';

// Server: require client certificates
const serverOptions = {
  key: fs.readFileSync('./certs/server.key'),
  cert: fs.readFileSync('./certs/server.crt'),
  ca: fs.readFileSync('./certs/ca.crt'),   // CA that issued client certs
  requestCert: true,    // Request client cert
  rejectUnauthorized: true,  // Reject if cert is invalid
};

https.createServer(serverOptions, app).listen(8443);

// In Express middleware — extract client identity from cert
app.use((req: any, res, next) => {
  const cert = req.socket.getPeerCertificate();
  if (!cert.subject) return res.status(401).send('Client cert required');
  req.serviceId = cert.subject.CN;  // e.g. 'orders-service'
  next();
});

# Client: present its own certificate when calling the server
# curl --cert client.crt --key client.key --cacert ca.crt https://api:8443/endpoint

Authentication Method Comparison

Method Stateless Revocable Best For Complexity
API Key No (DB lookup) Yes (delete from DB) Server-to-server, SDKs Low
JWT (Bearer) Yes Partial (via blocklist) User auth, microservices Medium
OAuth 2.0 (CC) Yes (JWT tokens) Yes (revoke refresh) M2M, background services Medium
OAuth 2.0 (Auth Code) Yes Yes User-facing web/mobile apps High
mTLS Yes (cert-based) Yes (CRL / OCSP) Zero-trust, internal services High
Share this tool: X / Twitter LinkedIn
✓ 60,000+ developers use DevPlaybook ★★★★★ 4.8 / 5 on Gumroad ✓ MIT licensed · Instant download ✓ No login required

Related Products

on Gumroad

You Might Also Need

More Free Tools

JWT Decoder OpenAPI Validator API Response Mock Generator API Response Time Tester JWT Builder OpenAPI Schema Validator OAuth2 Flow Visualizer OpenAPI Mock Generator OAuth2 Flow Debugger API Test Case Generator API Request Builder HTTP Security Headers Generator JWT Debugger Pro API Mock Server Config Generator HTTP Security Headers Checker API Key Generator API Rate Limit Calculator API Response Formatter & Analyzer HTTP API Tester LLM Cost Calculator OAuth2 Flow Tester AI Prompt Cost Calculator REST API Request Builder Content Security Policy Builder LLM Prompt Cost Calculator API Response Mocker HTTP Header Inspector AI Rate Limit Calculator Prompt Injection Tester GCP Service Account Key Auditor HTTP Headers Analyzer AI Doc Generator — Pro Tool AI System Guardian .htaccess Generator HTTP Methods Reference HTTP Code Snippet Generator HTTP Status Code Reference HTTP Status Code Reference Interview Question Generator JSON Diff Viewer JSON Diff nginx Config Generator Password Strength Checker Rate Limiter Tester API Changelog Generator HTTP Mock Response Builder HMAC Generator CSP Header Builder Rate Limit Calculator CORS Policy Builder AWS IAM Policy Validator Haproxy Config Validator HTTP Cookie Parser SBOM Generator HTTP Status Code Lookup HTTP Status Code Lookup Vue Component Generator HTTP Status Code Reference LLM Context Window Calculator HTTP Header Analyzer LLM Token Counter Dockerfile Linter mTLS Certificate Chain Builder Prompt Token Estimator Mock Service Worker (MSW) API Mocking Guide Pulumi Modern IaC Guide Casdoor Docker Layer Optimizer HTML5 Canvas API Playground Token Context Calculator GraphQL Client Configurator OpenAI Function Calling Generator Token Counter & Cost Estimator AI API Rate Limit Calculator Password Strength Analyzer

Enjoying this tool? Get more like it.

Weekly developer tool guides, productivity playbooks & AI tricks. Free. No spam.

DevPlaybook Pro 7 days free

Unlock AI-Powered Dev Tools

  • ⚡ AI Code Review, Doc Generator & SQL Builder
  • ⚡ All premium templates & early access
  • ⚡ Member discounts on Gumroad products
Start Free Trial →

$9/mo after trial · Cancel anytime

Want the full toolkit?

Get DevPlaybook Pro

Every template, guide, boilerplate, and automation script in one bundle. 13 premium products — grab them all at once and save big.

$79
$241 separately Save $162 (67% off)
Get DevPlaybook Pro — $79 →

MIT licensed · Instant download · No subscription

See what's included → Browse all deals →

Frequently Asked Questions

What is the difference between authentication and authorization?▼

Authentication (authn) answers 'Who are you?' — verifying the identity of the caller via credentials (password, token, certificate). Authorization (authz) answers 'What can you do?' — checking if the authenticated identity has permission to perform the requested operation. An API key authenticates a client; scope claims in a JWT authorize what that client can access. Always separate these concerns in your code.

When should I use API keys vs JWT?▼

Use API keys for server-to-server integrations where the client is a machine, the key is stored securely, and simplicity is valued over granularity. Use JWTs for user-facing authentication where you need to embed claims (user ID, roles, expiry) in the token itself without a database lookup on every request. JWTs are stateless; API keys require a database lookup to validate unless you use a hash-based scheme.

What are the OAuth 2.0 grant types and when should I use each?▼

Client Credentials: machine-to-machine, no user involved — use for background jobs and service accounts. Authorization Code (+ PKCE): user-facing apps — browser or mobile app redirects to identity provider, user logs in, app gets a short-lived code it exchanges for tokens. Implicit flow is deprecated. Device Code: TV or CLI apps without browsers. Refresh Token: exchange a long-lived refresh token for a new access token without re-authentication.

How long should JWT access tokens live?▼

Access tokens should be short-lived — typically 5 to 60 minutes. Short expiry limits the damage if a token is stolen. Pair short-lived access tokens with longer-lived refresh tokens (hours to days) that are rotated on use (refresh token rotation). Store refresh tokens securely: httpOnly cookies for web apps, secure storage (Keychain/Keystore) for mobile apps. Never store access or refresh tokens in localStorage.

What is mTLS and when is it used?▼

Mutual TLS (mTLS) is a form of authentication where both the client and server present X.509 certificates. Unlike standard TLS (only the server authenticates itself), mTLS verifies the client's identity at the TLS layer before any HTTP request is processed. It is used for service-to-service authentication in zero-trust architectures, Kubernetes pods using SPIFFE/SPIRE, and payment APIs. It provides very strong authentication without managing token expiry.

How do I revoke a JWT if it's stateless?▼

JWTs cannot be revoked without some server-side state. Common strategies: (1) Short expiry + refresh token rotation — a stolen access token expires quickly; revoke the refresh token in the database. (2) Token blocklist — maintain a Redis set of revoked JTI (JWT ID) claims; check on each request (adds a Redis lookup). (3) Token versioning — store a version number per user; JWTs include the version; increment the version to invalidate all existing tokens.