Every API needs a way to verify who's calling it and what they're allowed to do. Choosing the right authentication method is one of the most important security decisions you'll make. This guide covers the five most common approaches, their trade-offs, and when to use each.
Authentication vs Authorization
Before diving in, let's clarify the distinction:
- Authentication (AuthN): "Who are you?" — Verifying the identity of the caller
- Authorization (AuthZ): "What can you do?" — Determining what the authenticated caller is allowed to access
Most API security systems handle both. OAuth 2.0, for example, is primarily an authorization framework, but it's commonly used to handle authentication too (via OpenID Connect).
1. API Keys
The simplest approach: assign a unique key to each client and require it in every request.
// In header (preferred)
GET /api/data HTTP/1.1
X-API-Key: sk_live_abc123def456
// In query string (avoid — logged in server logs, browser history)
GET /api/data?api_key=sk_live_abc123def456
When to use
- Server-to-server communication
- Rate limiting and usage tracking by client
- Public APIs with usage tiers
Security considerations
- Never expose in client-side code (JavaScript, mobile apps)
- Always send via HTTPS
- Use prefixes to distinguish key types:
sk_live_(secret),pk_live_(public) - Support key rotation — let users generate new keys and revoke old ones
- Store keys hashed (like passwords), not in plaintext
2. Basic Authentication
Sends username and password encoded in Base64 with every request:
GET /api/data HTTP/1.1
Authorization: Basic cmFodWw6cGFzc3dvcmQxMjM=
// Base64 of "rahul:password123"
When to use
- Internal tools and quick prototypes
- Behind a VPN or private network
- When combined with HTTPS (never use over plain HTTP!)
When NOT to use
- Public APIs — credentials sent with every request increases exposure
- Mobile or SPA apps — no way to securely store passwords client-side
- When you need token revocation or refresh capabilities
3. Bearer Tokens (JWT)
JSON Web Tokens (JWT) are self-contained tokens that encode user identity and claims. They're the most popular token format for modern APIs.
GET /api/data HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxMjM0fQ.signature
JWT structure
A JWT has three parts separated by dots:
HEADER.PAYLOAD.SIGNATURE
Header: { "alg": "HS256", "typ": "JWT" }
Payload: { "user_id": 1234, "role": "admin", "exp": 1711234567 }
Signature: HMAC-SHA256(header + "." + payload, secret)
When to use
- SPAs and mobile apps after user login
- Microservices communicating between each other
- Stateless APIs that don't want server-side session storage
Security best practices
- Set short expiry: 15–30 minutes for access tokens
- Use refresh tokens: Longer-lived tokens stored securely to get new access tokens
- Validate all claims: Check
exp,iss,audon every request - Use RS256 over HS256 for public APIs — asymmetric keys let you share the public key for verification
- Never store sensitive data in payload: JWTs are signed, not encrypted. Anyone can decode the payload.
4. OAuth 2.0
OAuth 2.0 is a delegation framework. It lets users grant third-party apps limited access to their data without sharing passwords.
The Authorization Code Flow (most secure)
1. User clicks "Login with Google" in your app
2. App redirects to Google's authorization server
3. User logs in and grants permission
4. Google redirects back with an authorization code
5. Your server exchanges the code for an access token
6. Your server uses the token to call Google's API
OAuth 2.0 grant types
| Grant Type | Use Case | Security Level |
|---|---|---|
| Authorization Code + PKCE | SPAs, mobile apps | High |
| Authorization Code | Server-side web apps | High |
| Client Credentials | Machine-to-machine | Medium |
| Device Code | Smart TVs, CLIs | Medium |
When to use OAuth 2.0
- Third-party integrations ("Login with Google/GitHub")
- When users need to grant limited access to their data
- Enterprise SSO and federated identity
5. HMAC Signatures
HMAC (Hash-based Message Authentication Code) signs each request using a shared secret. Both client and server know the secret, and the signature proves the request wasn't tampered with.
// Client builds a string to sign:
string_to_sign = "GET\n/api/data\ntimestamp=1711234567"
// Client creates signature:
signature = HMAC-SHA256(string_to_sign, shared_secret)
// Client sends:
GET /api/data HTTP/1.1
X-Timestamp: 1711234567
X-Signature: a1b2c3d4e5f6...
When to use
- Webhook verification (Stripe, GitHub, Razorpay all use this)
- Server-to-server APIs where you need tamper-proof requests
- When you want to verify both identity AND request integrity
Comparison Table
| Method | Complexity | Stateless? | Best For |
|---|---|---|---|
| API Keys | Low | Yes | Server-to-server, rate limiting |
| Basic Auth | Low | Yes | Internal tools, prototypes |
| JWT | Medium | Yes | SPAs, mobile, microservices |
| OAuth 2.0 | High | Depends | Third-party access, SSO |
| HMAC | Medium | Yes | Webhooks, tamper-proof APIs |
Security Checklist
- ✅ Always use HTTPS — never send credentials over HTTP
- ✅ Rotate secrets and keys regularly
- ✅ Use short-lived tokens with refresh mechanisms
- ✅ Validate tokens server-side on every request
- ✅ Rate limit authentication endpoints to prevent brute force
- ✅ Log authentication failures for monitoring
- ✅ Use PKCE for public clients (SPAs, mobile)
- ✅ Hash API keys before storing in your database