HomeArticlesGuideAPI Security Guide
GUIDE

API Security Guide — Keys, Auth & Best Practices

Practical API security guide for developers — generating secure API keys, implementing JWT authentication, hashing secrets, and validating inputs end-to-end.

Reviewed by the thecalcu.com team · Last updated August 4, 2026

Overview

API security is a discipline where a mistake usually costs someone other than the person who made it: exposed user data, a compromised partner system, or damage to your company's reputation and legal standing. The upside is that the core defenses aren't mysterious. Generate keys with enough entropy, store credentials as hashes rather than raw values, keep tokens short-lived, encrypt every connection, and validate whatever comes through the door.

This guide walks through six practices that cover most real-world API security incidents. Each step explains not only what to do but which specific parameters to use in 2026, and the tools linked along the way let you generate keys, validate JWTs, test hash functions, and check certificate setups right in your browser, no extra software required.

Security isn't something you configure once and forget. Threats shift, libraries pick up new vulnerabilities, and even a securely generated credential eventually needs rotating. The monitoring and rotation habits in Step 6 matter as much as the setup work in Steps 1 through 5. A system configured perfectly on day one but never watched afterward will get compromised eventually.


Step 1: Generate and Manage API Keys

API keys are usually the first credential a developer implements and the one thought through least carefully. A poorly generated key is about as dangerous as no key at all if it can be guessed or enumerated.

Entropy requirements:

Generate a secure API key from a cryptographically secure random number generator (CSPRNG), not Math.random(), not UUID v4, and not anything seeded from a timestamp. The floor is 32 bytes, or 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 so keys are identifiable at a glance and secret-scanning tools can catch them if they leak into logs or repos:

sk_live_a3f9c2e8b1d4...   (secret key, live environment)
pk_live_7b2d1a9e4c3f...   (publishable key, live)
sk_test_f1e8d2c9b5a4...   (secret key, test environment)

The prefix also lets you log or display a partial key safely for debugging, without exposing the working credential.

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. The pattern that works:

  1. Generate the key and show it to the user once, and only once.
  2. Compute its SHA-256 hash.
  3. Store { key_prefix: "sk_live_a3f9c2", key_hash: "<sha256>", created_at, expires_at, scopes }.
  4. On each request, look up by prefix, hash the presented key, and compare in constant time.

Constant-time comparison matters here. Use crypto.timingSafeEqual() in Node.js or hmac.compare_digest() in Python so a timing side-channel can't leak information about the correct hash.


Step 2: Implement Authentication with JWT

JWT (JSON Web Token) is a compact way to pass authentication claims between parties. It's everywhere in API authentication, and also frequently implemented in ways that open real vulnerabilities.

Token structure:

A JWT has three Base64URL-encoded parts joined by dots: Header.Payload.Signature. The header names the algorithm, the payload holds claims like sub (subject or user ID), exp (expiry), iss (issuer), and aud (audience), and the signature confirms nothing's been tampered with.

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 the alg:none vulnerability below

Reach for RS256 or ES256 whenever multiple services verify tokens but only the auth server should be able to sign them. Verifying services only get the public key under these schemes, so they can confirm a token's valid without ever gaining the power to forge one.

Token lifetime:

  • Access token: 15 to 60 minutes at most, short enough that a stolen token expires before anyone can put it to real use.
  • Refresh token: 7 to 30 days, kept in an HttpOnly, Secure cookie rather than localStorage, which JavaScript (and therefore XSS) can read.
  • Refresh tokens should rotate on use: each one issued invalidates the last.

Mandatory validation checks:

Before trusting a JWT, check every one of these:

  1. The alg header matches what you expect; reject none outright.
  2. The signature verifies against the correct key.
  3. exp is still in the future.
  4. nbf, if present, is already in the past.
  5. iss matches your expected issuer.
  6. aud matches your service identifier.

Use the JWT Validator to inspect and validate tokens during development, debugging, or an incident investigation.


Step 3: Hash Secrets and Passwords Properly

The most damaging credential-storage mistake is saving a password or secret in a form that can be reversed or cracked once a database leaks.

Password hashing, and why it needs to be slow:

General-purpose hash functions like SHA-256, SHA-1, and MD5 are built for speed, and speed is the wrong property for a password hash: it lets an attacker holding a leaked hash try billions of guesses per second.

The right algorithms for passwords:

Algorithm Parameters (2026) Notes
bcrypt cost factor ≥ 12 Widely supported, about 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 roughly 250ms per hash, which a user logging in once won't notice but which multiplies an attacker's offline cracking time by 2^12 compared to unsalted SHA-256.

API keys and token hashing: SHA-256 is fine here:

For API keys, session tokens, and password reset tokens (not passwords), SHA-256 works well. These strings already carry high entropy, and you need deterministic lookups when a key comes in, which a slow algorithm would make painfully slow. Salting adds nothing once a secret already has 256 bits of entropy; a rainbow table for values that large simply isn't feasible to build.

Use the Hash Generator to test hash functions, check expected outputs, and compare algorithms while you build.

Salting:

A salt is a random value mixed into a password before hashing, so two users with the same password end up with different stored hashes. Bcrypt, Argon2id, and scrypt all generate and store this salt for you automatically. Skip any scheme that relies on a fixed "pepper" without a random salt underneath it.


Step 4: Enforce HTTPS and Certificate Hygiene

All API traffic needs to run over TLS (Transport Layer Security). Sending credentials, tokens, or user data over plain HTTP in 2026 isn't a defensible choice.

Minimum TLS requirements:

  • Require TLS 1.2 or 1.3, and turn off TLS 1.0 and 1.1 entirely; both carry known protocol flaws (POODLE, BEAST, CRIME among them).
  • Prefer TLS 1.3 wherever you can: it strips out 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 header so browsers and HTTP clients never fall back to plain HTTP for your domain:

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

Start with max-age=300 (five minutes) to confirm it works, then raise it to 31536000 (one year). Submit your domain to the HSTS preload list at hstspreload.org so browsers enforce it before the very first connection.

Certificate management:

  • Use a 2048-bit RSA or 256-bit ECDSA certificate. ECDSA is the better pick: smaller, faster, and just as secure.
  • Automate renewal, whether through Let's Encrypt and certbot, AWS ACM, or Caddy's built-in ACME support. Manual renewal is a reliability risk waiting to happen.
  • For internal services or client certificate auth, the CSR Generator generates certificate signing requests correctly.
  • If you're pinning certificates in a mobile app, pin the intermediate CA, not the leaf certificate, so routine leaf rotation doesn't break the app.

Redirect HTTP to HTTPS at the infrastructure level (load balancer or reverse proxy), not inside the application. An application-level redirect fails the moment the application crashes; the infrastructure one doesn't.


Step 5: Validate All Inputs and Rate Limit

Input validation and rate limiting aren't secondary concerns tacked on at the end. They're what stands between your API and injection attacks, credential-stuffing brute force, and denial-of-service through resource exhaustion.

Input validation principles:

Use an allowlist: define exactly what valid input looks like and reject anything that doesn't 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
}

A few rules worth following:

  • Enforce type on every field and reject mismatches.
  • Cap the max length on every string field to head off memory exhaustion.
  • Validate format, email, URL, date, before you process anything.
  • Strip or reject fields you don't recognize rather than passing them through.
  • Sanitize before rendering, not before storing; XSS is a rendering problem, not a storage one.

Rate limiting:

Apply it at two levels. Per API key, cap request volume over a sliding window, say 1,000 requests per minute, to stop one key from exhausting resources on its own. Per IP address, limit unauthenticated requests and login attempts, say 10 per hour, to blunt credential stuffing.

When a limit trips, respond with:

HTTP 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1751000000

Put rate limiting at the API gateway or reverse proxy layer (Nginx, Kong, AWS API Gateway), not only in the application, which someone can bypass by hitting the infrastructure directly.

Set maximum request body size and URL length at the load balancer too. It's a separate control from rate limiting, but it closes off oversized-payload attacks the same way.


Step 6: Monitor, Audit, and Rotate

An API secured perfectly on day one is only as safe as the first anomaly nobody notices. Logging, alerting, and routine rotation are what separate catching a breach within hours from finding out about 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, personal fields like email or phone in request bodies, or card numbers. The key prefix (first 6-8 characters) gives you traceability without exposing anything usable.

Alerting triggers:

Set alerts for a few specific patterns: a key that's only ever run from Mumbai suddenly making requests from a European IP; volume from a key spiking between 2 and 5 AM local time well past its 30-day baseline; more than 10 consecutive 401 or 403 responses from one IP within 5 minutes; overall 5xx rate above 1% for more than 3 minutes; and a key that normally hits read endpoints suddenly reaching for write or admin ones.

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

Rotate with a grace period: generate the new credential, share it, run both in parallel for 24 to 72 hours, confirm the new one's actually in use, then revoke the old one. That avoids the downtime an instant swap would cause.

Keep a revocation record (a revoked_at timestamp on the key row is enough) and check it on every request. A revoked key should return 401 immediately, with a message telling the developer to generate a new one.


Key Terms

JWT (JSON Web Token): a compact, URL-safe token made up of a header, payload, and signature, used to carry authentication claims between parties in a stateless API.

API Key: a unique credential string an API provider issues to identify and authenticate a client or service. Treat it as a secret; never expose it in client-side code.

Hash: the output of a one-way function that turns input of any length into a fixed-length digest. The same input always hashes the same way, and there's no reversing a hash back to its original input.

Salt: a random value mixed into a password before hashing, so two users with identical passwords get different stored hashes, which blocks rainbow table attacks.

TLS (Transport Layer Security): the cryptographic protocol behind encrypted, authenticated network communication, and the successor to the deprecated SSL. API traffic should run on TLS 1.2 or 1.3.

HTTPS: HTTP carried over a TLS connection, giving you encryption in transit. Required for any production API or any service handling credentials or user data.

Rate Limiting: a control that caps how many requests a client can make in a given window, protecting an API from abuse, brute force, and denial-of-service.

HSTS (HTTP Strict Transport Security): a response header that tells clients to connect to a domain only over HTTPS, closing off protocol downgrade attacks.

Entropy: a measure of randomness in a value, in bits. More entropy means more possible values, and more resistance to guessing.

Frequently Asked Questions

How long should an API key be and why?
A secure API key needs at least 32 bytes (256 bits) of cryptographically random data, usually encoded as a 64-character hex string or a 44-character Base64 string. That gives you 2^256 possible values, which puts brute-force guessing well beyond what any realistic hardware can attempt. UUID v4 falls short of this bar: it carries only about 122 bits of real randomness, and its structured format leaks information about how it was generated.
Should I store API keys in plain text in my database?
No. Store only the SHA-256 hash of the key, alongside a short prefix (the first 8 characters or so) that lets you look the record up. When a client sends a key, hash the incoming value and compare it against the stored hash. A database breach then exposes nothing an attacker could actually use to authenticate.
What is the difference between HS256 and RS256 in JWT?
HS256 (HMAC-SHA256) signs and verifies with one shared secret, so every service that checks tokens also needs that secret, and compromising any one of them puts the whole system at risk. RS256 (RSA-SHA256) signs with a private key and verifies with a public one, so verifying services only ever hold the public half and can't forge new tokens. For distributed systems with several independent services, RS256 is the safer choice.
How short should JWT access token expiry be?
Keep it between 15 and 60 minutes for most production APIs. A short-lived token limits the damage from theft, since it expires before an attacker can put it to wide use. Pair it with a longer-lived refresh token (7 to 30 days), stored somewhere safer than localStorage, like an HttpOnly cookie, and used only to fetch new access tokens from a dedicated endpoint.
Can I use SHA-256 to hash user passwords?
You shouldn't. SHA-256, SHA-1, and MD5 are built to run fast, and that speed is exactly what makes them vulnerable to brute-force and dictionary attacks even with a salt attached. Passwords need a slow, purpose-built algorithm instead: bcrypt at a cost factor of 12 or higher, Argon2id with adequate memory and iteration settings, or scrypt. Their deliberate slowness is what makes large-scale offline cracking impractical.
What is entropy in the context of API keys and passwords?
Entropy measures randomness in bits. A key with N bits of entropy has 2^N possible values, so a 256-bit API key (32 random bytes) has so many that trying them all is out of the question. Compare that to a 6-character password using only lowercase letters: 26^6, roughly 300 million values, or about 28 bits, which a modern machine cracks in seconds. The [API Key Generator](/api-key-generator/) produces keys with guaranteed high entropy by default.
What should I do if an API key is exposed in a public repository?
Treat it as compromised the moment you find it. Revoke it immediately from your API dashboard rather than waiting for a scheduled rotation, and check your access logs for the window between the commit and the revocation for signs of unauthorized requests. Turn on GitHub secret scanning (or an equivalent) so future exposures trigger an alert automatically, and generate a replacement key with a proper secure generator, stored only in environment variables or a secrets manager, never in code.
What HTTP status code should I return when rate limiting triggers?
Return 429 Too Many Requests, along with a Retry-After header telling the client how many seconds to wait. Include a plain-language message in the response body describing the limit, something like '100 requests per minute per API key.' Log the event with the key's identifier (never the key itself), the endpoint hit, and the timestamp, so an abuse investigation has something to work from.
What TLS version should my API require in 2026?
TLS 1.2 or 1.3, with TLS 1.0 and 1.1 explicitly disabled, since both carry known vulnerabilities including POODLE and BEAST. TLS 1.3 is the better default where you can use it: it drops weak cipher suites entirely and gives you forward secrecy by design. Set your load balancer or reverse proxy, whether that's Nginx, Caddy, or an AWS ALB, to enforce the minimum version and a strong cipher list, and never let unencrypted HTTP reach your API at all.
What is the alg:none vulnerability in JWT?
It shows up when a JWT library accepts a token whose header sets the algorithm to 'none,' meaning no signature check happens at all. An attacker can then craft a token with whatever claims they want and label it 'none'; a careless verifier waves it through. Your validation code needs to name the algorithms it will accept explicitly and reject anything else, including 'none,' every time.
How do I implement API key rotation without breaking existing integrations?
Run a grace period: issue the new key, hand it to whoever needs it, and keep the old one live for an overlap window, 24 to 72 hours for automated systems or up to two weeks where a human has to update something manually. Watch usage on both keys in your logs, and once the old one goes quiet, revoke it. This avoids downtime for integrations that can't switch over instantly and gives the operator time to confirm the new key actually works.
What should I log for every API request for security purposes?
Timestamp in UTC, request ID, the key's identifier prefix (never the full key), IP address, region, HTTP method, endpoint path with sensitive query parameters stripped out, response code, and latency. Skip logging request bodies that might carry passwords or personal data. Then set alerts for unusual regions, after-hours volume spikes, repeated 401 or 403 responses from one source, and error rates climbing above your normal baseline.

Related Articles

BEST OF

Best Password Generators Online 2026

HOW TO

How to Generate a Strong Password

HOW TO

How to Decode a JWT Token

COMPARISON

MD5 vs SHA-256 vs bcrypt — Hashing Algorithm Comparison

GUIDE

Security & Identity Validators: Passwords, Barcodes & Crypto Addresses