Overview
API security is a discipline where the cost of getting it wrong is almost always paid by someone other than the person who made the mistake — either your users whose data is exposed, your partners whose systems are compromised, or your company's reputation and legal standing. The good news is that the foundational controls are well understood: generate keys with sufficient entropy, store credentials as hashes, use short-lived tokens, encrypt all traffic, and validate every input.
This guide walks through the six core practices that account for the vast majority of real-world API security incidents. Each step is actionable — it describes not just what to do but how to do it and what specific parameters to use in 2026. The tools linked throughout can help you generate secure keys, validate JWTs, test hash functions, and check certificate configurations directly in your browser without installing additional software.
One important framing note: security is not a one-time implementation. Threats evolve, libraries acquire vulnerabilities, and credentials that were generated securely eventually need rotation. The practices in Step 6 (monitoring, auditing, rotating) are as important as the implementation practices in Steps 1–5 — a system with perfect initial configuration that is never monitored will eventually be compromised.
Step 1: Generate and Manage API Keys
API keys are the credential most developers implement first and think about least carefully. A poorly generated key is as dangerous as no key at all if it can be guessed or enumerated.
Entropy requirements:
A secure API key must be generated from a cryptographically secure random number generator (CSPRNG) — not Math.random(), not UUID v4, and not a timestamp-seeded generator. The minimum standard is 32 bytes (256 bits) of random data.
In practice:
// Node.js — correct
const crypto = require('crypto');
const apiKey = crypto.randomBytes(32).toString('hex'); // 64-char hex string
// Python — correct
import secrets
api_key = secrets.token_hex(32) # 64-char hex string
// Wrong — do NOT use
const apiKey = uuid.v4(); // Only 122 bits of entropy, structured format
Key structure and prefixes:
Adopt a prefixed format that makes keys identifiable at a glance and enables automated secret scanning to catch them if they leak into logs or repositories:
sk_live_a3f9c2e8b1d4... (secret key, live environment)
pk_live_7b2d1a9e4c3f... (publishable key, live)
sk_test_f1e8d2c9b5a4... (secret key, test environment)
The prefix also allows partial disclosure for debugging — you can safely log and display the prefix without exposing the actual key.
Use the API Key Generator to generate properly structured, high-entropy keys instantly for development and testing.
Storage and lookup:
Never store the raw key in your database. The correct pattern:
- Generate key → show to user once (and only once).
- Compute SHA-256 hash of the key.
- Store:
{ key_prefix: "sk_live_a3f9c2", key_hash: "<sha256>", created_at, expires_at, scopes } - On incoming request: look up by prefix, compute SHA-256 of the presented key, compare in constant time (timing-safe equality).
Constant-time comparison is critical — use crypto.timingSafeEqual() in Node.js or hmac.compare_digest() in Python to prevent timing oracle attacks.
Step 2: Implement Authentication with JWT
JWT (JSON Web Token) is a compact, self-contained mechanism for transmitting authentication claims between parties. It is widely used for API authentication but frequently misimplemented in ways that create serious vulnerabilities.
Token structure:
A JWT has three Base64URL-encoded parts separated by dots: Header.Payload.Signature.
The header declares the algorithm; the payload contains claims like sub (subject/user ID), exp (expiry), iss (issuer), and aud (audience); the signature verifies integrity.
Algorithm choice:
| Algorithm | Type | Recommended Use |
|---|---|---|
| RS256 | Asymmetric (RSA) | Recommended for distributed systems |
| ES256 | Asymmetric (ECDSA) | Recommended — smaller keys than RS256 |
| HS256 | Symmetric (HMAC) | Only for single-service or monolith scenarios |
| none | No signature | Never — see alg:none vulnerability |
Use RS256 or ES256 when multiple services need to verify tokens but only one service (the auth server) should be able to sign them. With asymmetric algorithms, verifying services receive only the public key — they can confirm a token is valid but cannot forge new ones.
Token lifetime:
- Access token: 15–60 minutes maximum. Short enough that a stolen token expires before widespread misuse.
- Refresh token: 7–30 days, stored in an HttpOnly, Secure cookie (not localStorage, which is accessible to JavaScript and therefore XSS-vulnerable).
- Refresh tokens should be single-use (rotation): each use issues a new refresh token and invalidates the old one.
Mandatory validation checks:
When validating a JWT, check all of the following:
algheader matches the expected algorithm — rejectnone- Signature is valid using the correct key
expclaim is in the future (token not expired)nbfclaim (not before) is in the past if presentiss(issuer) matches your expected issueraud(audience) matches your service identifier
Use the JWT Validator to inspect and validate JWT tokens during development, debugging, or incident investigation.
Step 3: Hash Secrets and Passwords Properly
The most common and most damaging type of credential storage error is saving passwords or secrets in a form that can be reversed or cracked if the database is breached.
Password hashing — use a slow algorithm:
General-purpose hash functions (SHA-256, SHA-1, MD5) are designed to be fast. Fast is exactly wrong for passwords — it allows an attacker with a leaked hash database to try billions of guesses per second.
The correct algorithms for passwords:
| Algorithm | Parameters (2026) | Notes |
|---|---|---|
| bcrypt | cost factor ≥ 12 | Widely supported, ~250ms at factor 12 |
| Argon2id | 64MB memory, 3 iterations | Current OWASP recommendation |
| scrypt | N=32768, r=8, p=1 | Strong, slightly less library support |
// bcrypt in Node.js
const bcrypt = require('bcrypt');
const saltRounds = 12;
const hash = await bcrypt.hash(password, saltRounds);
// Verify
const match = await bcrypt.compare(inputPassword, storedHash);
Cost factor 12 adds approximately 250ms per hash — imperceptible to a user logging in once, but multiplies the attacker's offline cracking time by 2^12 compared to an unsalted SHA-256.
API keys and token hashing — SHA-256 is correct:
For API keys, session tokens, and password reset tokens (not passwords), SHA-256 is appropriate. These are long, high-entropy random strings where speed and determinism are desirable — you need to look up a matching hash when the key is presented. Salting is unnecessary when the secret has 256 bits of entropy (a rainbow table for 256-bit values is computationally infeasible).
Use the Hash Generator to test hash functions, verify expected outputs, and compare algorithms during development.
Salting:
A salt is a random value added to a password before hashing that ensures two identical passwords produce different hashes. Bcrypt, Argon2id, and scrypt all generate and store a salt automatically — you do not manage it separately. Never use a fixed ("pepper") salt without a random salt on top.
Step 4: Enforce HTTPS and Certificate Hygiene
All API traffic must travel over TLS (Transport Layer Security) — transmitting credentials, tokens, or user data over unencrypted HTTP is indefensible in 2026.
Minimum TLS requirements:
- Require TLS 1.2 or 1.3. Disable TLS 1.0 and 1.1 entirely — both have known protocol vulnerabilities (POODLE, BEAST, CRIME).
- TLS 1.3 should be preferred where possible — it removes weak cipher suites at the protocol level and provides forward secrecy by default.
- Configure strong cipher suites. In Nginx:
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305;
HSTS (HTTP Strict Transport Security):
Add the HSTS response header to instruct browsers and HTTP clients never to use plain HTTP for your domain:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Start with max-age=300 (5 minutes) to test, then increase to 31536000 (1 year) once confirmed working. Submit to the HSTS preload list (hstspreload.org) for browsers to enforce HSTS before the first connection.
Certificate management:
- Use a 2048-bit RSA or 256-bit ECDSA certificate. ECDSA is preferred — smaller, faster, equally secure.
- Automate certificate renewal (Let's Encrypt via certbot, AWS ACM, Caddy's built-in ACME). Manual renewal is a reliability risk.
- For internal services and client certificate auth, use the CSR Generator to generate certificate signing requests correctly.
- Certificate pinning in mobile apps: pin the intermediate CA certificate (not the leaf certificate) to survive routine leaf cert rotation.
Redirect HTTP to HTTPS at the infrastructure level (load balancer or reverse proxy), not at the application layer. An application-level redirect can fail if the application crashes — the infrastructure redirect cannot.
Step 5: Validate All Inputs and Rate Limit
Input validation and rate limiting are not secondary concerns — they are foundational to preventing injection attacks, brute-force credential stuffing, and denial-of-service via resource exhaustion.
Input validation principles:
Use a whitelist (allowlist) approach: define exactly what valid input looks like and reject anything that does not match.
// Whitelist validation example
function validateApiKeyRequest(body) {
const schema = {
name: { type: 'string', maxLength: 100, pattern: /^[a-zA-Z0-9 _-]+$/ },
scopes: { type: 'array', items: ['read', 'write', 'admin'], maxItems: 10 },
expiresAt: { type: 'date', min: new Date(), max: addDays(new Date(), 365) }
};
// Reject any field not in schema (strip unknown fields)
// Reject any value that doesn't match type/pattern/bounds
}
Key validation rules:
- Enforce type (string, number, array, boolean) — reject type mismatches
- Enforce max length on every string field — prevents memory exhaustion
- Validate format (email regex, URL format, date format) before processing
- Strip or reject unknown fields — do not pass-through unvalidated data
- Sanitise before rendering, not before storing (XSS is a rendering concern, not a storage concern)
Rate limiting:
Implement rate limiting at two levels:
- Per API key: Limit by request volume over a sliding window (e.g., 1,000 requests per minute). This prevents an individual key from causing resource exhaustion.
- Per IP address: Limit unauthenticated requests and authentication attempts (e.g., 10 login attempts per hour per IP). This prevents credential stuffing.
When the limit is exceeded, respond with:
HTTP 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1751000000
Rate limiting should be implemented at the API gateway or reverse proxy layer (Nginx, Kong, AWS API Gateway) — not only at the application layer, which can be bypassed by hitting infrastructure directly.
Input length limits at the load balancer level are a separate but complementary control — configure maximum request body size and maximum URL length to prevent oversized payload attacks.
Step 6: Monitor, Audit, and Rotate
A perfectly secured API that is never monitored is only secure until the first anomaly that goes undetected. Operational security practices — logging, alerting, and regular rotation — are the difference between catching a breach in hours and discovering it months later.
What to log on every request:
{
"timestamp": "2026-06-26T09:14:32.000Z",
"requestId": "req_f3a9b2c1",
"keyPrefix": "sk_live_a3f9c2",
"ipAddress": "103.21.244.0",
"region": "IN-MH",
"method": "POST",
"path": "/v1/payments",
"statusCode": 200,
"latencyMs": 87
}
Never log: the full API key, passwords, PII fields (email, phone, name) in request bodies, or credit card numbers. Log the key prefix (first 6–8 characters) for traceability without exposure.
Alerting triggers:
Set automated alerts for:
- New geographic region: A key that has only ever been used from Mumbai suddenly makes requests from a European IP.
- After-hours spike: Volume from a key between 2–5 AM local time significantly exceeds the 30-day baseline.
- Authentication failure surge: More than 10 consecutive 401/403 responses from the same IP within 5 minutes.
- Error rate spike: Overall 5xx rate exceeds 1% for more than 3 minutes.
- Unusual endpoint pattern: A key that typically hits read endpoints begins hitting write or admin endpoints.
Rotation schedule:
| Credential Type | Recommended Rotation | Trigger for Immediate Rotation |
|---|---|---|
| API keys (user-facing) | Annually or on role change | Any suspected exposure |
| API keys (service-to-service) | Every 90 days | Any suspected exposure |
| JWT signing keys (symmetric) | Every 90 days | Any compromise |
| JWT signing keys (asymmetric) | Annually | Any compromise |
| TLS certificates | At expiry (automate) | Any CA compromise |
Implement a grace period for rotation: generate new → share new → run both in parallel for 24–72 hours → verify new key in use → revoke old. This prevents downtime from instantaneous key replacement.
Maintain a key revocation list or revocation status in your database (a revoked_at timestamp on the key record is sufficient) and check it on every request. A key that has been revoked should return HTTP 401 immediately, with a clear error message directing the developer to generate a new key.
Key Terms
JWT (JSON Web Token) — A compact, URL-safe token format consisting of a header, payload, and signature, used to transmit authentication claims between parties in a stateless API architecture.
API Key — A unique credential string issued by an API provider to identify and authenticate a calling client or service. Should be treated as a secret and never exposed in client-side code.
Hash — The output of a one-way cryptographic function that converts an input of arbitrary length to a fixed-length digest. Identical inputs always produce identical hashes; hashes cannot be reversed to recover the original input.
Salt — A random value added to a password before hashing to ensure that two users with the same password produce different stored hashes, preventing rainbow table attacks.
TLS (Transport Layer Security) — The cryptographic protocol that provides encrypted, authenticated communication over a network, replacing the deprecated SSL protocol. All API traffic should use TLS 1.2 or 1.3.
HTTPS — HTTP transmitted over a TLS connection, providing encryption in transit. Mandatory for all production APIs and any service handling credentials or user data.
Rate Limiting — A control that restricts the number of requests a client can make within a defined time window, protecting APIs from abuse, brute-force attacks, and denial-of-service.
HSTS (HTTP Strict Transport Security) — An HTTP response header instructing clients to only connect to a domain over HTTPS, preventing protocol downgrade attacks.
Entropy — A measure of randomness or unpredictability in a value, expressed in bits. Higher entropy means more possible values and greater resistance to guessing attacks.