StringToolsStringTools
Back to Blog
SecurityApril 11, 2026·10 min read·StringTools Team

API Security Best Practices (2026): OWASP Top 10, Auth, Rate Limiting

API Breaches Are the Story of the Decade

In January 2025, a major telecom confirmed that an unauthenticated API endpoint exposed call metadata for 110 million customers — the attackers did not exploit a zero-day, they just enumerated IDs. In 2024, the Dell "partner portal" API leaked 49 million customer records the same way. Optus (2022), T-Mobile (2023), Twitter (2023) — every one of these headline breaches was an API security failure, not a network perimeter failure.

APIs are the attack surface now. Gartner projected that by 2025, API abuse would be the most frequent attack vector — that prediction has held. The Salt Security 2024 State of API Security report found that 95% of organizations had an API security incident in the past 12 months, and 23% suffered a data breach as a result.

The OWASP API Security Top 10 (2023 edition) codifies the patterns. BOLA (Broken Object Level Authorization) tops the list — the Dell and telecom breaches above are both BOLA. Broken authentication, excessive data exposure, lack of rate limiting, and SSRF round out the common findings.

This guide is a working engineer's playbook for hardening a production API in 2026. Every practice here is mapped to the OWASP API Top 10, includes runnable code, and references real incidents. If you implement even 7 of the 10 practices below, your API will be more secure than 95% of what is in production today.

What API Security Actually Means

API security is the practice of protecting an API's confidentiality, integrity, and availability across four dimensions:

- Authentication: proving who is calling (is this really user 1234?) - Authorization: proving they are allowed to do this action on this resource (can user 1234 read invoice 9876?) - Transport: encrypting data in flight (TLS 1.3) - Abuse prevention: limiting how much, how fast, and what shape traffic you accept (rate limiting, validation, WAF)

The OWASP API Security Top 10 (2023) organizes the common failures: API1 Broken Object Level Authorization, API2 Broken Authentication, API3 Broken Object Property Level Authorization, API4 Unrestricted Resource Consumption, API5 Broken Function Level Authorization, API6 Unrestricted Access to Sensitive Business Flows, API7 SSRF, API8 Security Misconfiguration, API9 Improper Inventory Management, API10 Unsafe Consumption of APIs.

Every practice below defends against one or more of these.

1. TLS 1.3 Everywhere (OWASP API8)

HTTP in 2026 is negligence. Every endpoint — including health checks and internal service-to-service calls — must be TLS. TLS 1.2 is acceptable; TLS 1.3 (RFC 8446) is recommended and supported by all modern clients. TLS 1.0 and 1.1 are deprecated and must be disabled.

Real incident: in 2023, a fintech startup leaked production API keys because their mobile app pinned to a TLS 1.1 endpoint that a middleman could downgrade.

Nginx config for TLS 1.3 only:

ssl_protocols TLSv1.3 TLSv1.2; ssl_ciphers 'TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256'; ssl_prefer_server_ciphers off; ssl_session_cache shared:SSL:10m; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

Verify your config with SSL Labs (ssllabs.com/ssltest). Aim for A+ — anything less has room for improvement.

2. Strong Authentication: API Keys vs OAuth vs JWT vs mTLS (OWASP API2)

Pick the right authentication for the context. Four common options:

API keys — Best for: server-to-server B2B integration • Pros: simple • Cons: no expiry, hard to rotate, single-factor. Use with IP allowlisting.

OAuth 2.0 — Best for: third-party app access on behalf of a user • Pros: scoped access, delegation, revocable • Cons: complex flows. Use PKCE for SPAs/mobile (RFC 7636).

JWT (Bearer tokens) — Best for: stateless microservices, SPAs • Pros: fast verification via public key • Cons: hard to revoke, size overhead. 5-15 min access tokens + refresh tokens.

mTLS (Mutual TLS) — Best for: service-to-service inside a trust zone, high-assurance B2B • Pros: no credentials in payload, phishing-resistant • Cons: cert lifecycle operational overhead. SPIFFE/SPIRE automate this.

Never: Basic Auth over anything but internal mTLS. HTTP Basic was designed in 1996 and does not fit modern threat models.

Minimum bar: enforce authentication on every endpoint except /health and /metrics. A single unauthenticated endpoint that returns user data is how the 2024 Dell breach happened.

3. Object-Level Authorization: The #1 API Vulnerability (OWASP API1)

Authenticating a user is not enough. You must check that this user is allowed to access this object. The classic BOLA bug:

// VULNERABLE — any authenticated user can read any invoice app.get('/invoices/:id', auth, async (req, res) => { const invoice = await db.invoices.findById(req.params.id); res.json(invoice); });

// SECURE — scope the query to the caller app.get('/invoices/:id', auth, async (req, res) => { const invoice = await db.invoices.findOne({ where: { id: req.params.id, userId: req.user.id } }); if (!invoice) return res.status(404).end(); res.json(invoice); });

Apply this pattern on every endpoint. Use UUIDs or nanoids instead of sequential integers so attackers cannot enumerate. For multi-tenant SaaS, scope by tenant_id at the database query level or use Postgres Row-Level Security policies as a belt-and-suspenders defense.

For role-based checks on admin-only endpoints (OWASP API5), use a middleware:

function requireRole(role) { return (req, res, next) => req.user.role === role ? next() : res.status(403).end(); } app.delete('/users/:id', auth, requireRole('admin'), handler);

4. Input Validation with Schemas (OWASP API3, API10)

Never trust input. Every request body, query param, and header must be validated against a schema before it reaches your business logic.

Modern tools:

- Zod (TypeScript) — type-safe runtime validation, integrates with Express/Fastify/tRPC - Joi — mature, feature-rich Node validation - Yup — similar to Joi, React-friendly - JSON Schema — language-agnostic (Ajv for Node, python-jsonschema for Python) - Pydantic — Python, the FastAPI default

Example with Zod:

import { z } from 'zod';

const CreateUserSchema = z.object({ email: z.string().email().max(255), password: z.string().min(12).max(128), age: z.number().int().min(13).max(120), role: z.enum(['user', 'editor']) // 'admin' not allowed from client });

app.post('/users', (req, res) => { const result = CreateUserSchema.safeParse(req.body); if (!result.success) return res.status(400).json(result.error); createUser(result.data); });

Key principles: allowlist, don't blocklist. Reject unknown fields (prevents mass assignment). Enforce max lengths to prevent DoS. Validate enums for state fields. Never concatenate user input into SQL — always use parameterized queries.

5. Rate Limiting and Abuse Prevention (OWASP API4)

An API without rate limiting is a free DoS vector and a free credential-stuffing platform. Three algorithms, pick by use case:

Fixed window — Simple: 100 requests per 60 seconds per IP. Edge case: burst at boundary (200 requests in 2 seconds).

Sliding window — Weighted combination of previous and current window. Smoother than fixed window.

Token bucket — Each client has a bucket that refills at a steady rate. Allows bursts, enforces steady-state. Used by AWS API Gateway, Stripe.

Leaky bucket — Like token bucket but enforces output rate regardless of input. Good for downstream service protection.

Implementation options:

- express-rate-limit (Node) — fixed/sliding window, Redis-backed for multi-instance - @upstash/ratelimit — serverless-friendly, edge-compatible - nginx limit_req_zone — at the proxy layer - Cloudflare Rate Limiting, AWS WAF — at the CDN layer

Tiered limits:

- Anonymous: 30/min per IP - Authenticated: 300/min per user - Login endpoint: 5/min per IP + 5/min per email - Password reset: 3/hour per email

Always return 429 Too Many Requests with Retry-After header. Log excessive rate-limit hits — they are your earliest signal of an attack.

6. Minimal Responses and Property-Level Authorization (OWASP API3)

The 2019 Facebook API leak exposed 540 million records because internal fields were included in a public response. Default to returning the minimum.

Patterns:

- Explicit serializers (DTOs). Define what leaves your API, not what lives in the DB. - GraphQL field-level auth. Don't rely solely on schema — enforce permissions per field in resolvers. - Strip sensitive fields at the ORM level. Set password, internal_notes, ssn to hidden columns.

Bad:

res.json(await db.users.findById(id)); // returns password hash, internal_notes, ssn

Good:

function toUserDTO(u) { return { id: u.id, email: u.email, name: u.name, createdAt: u.createdAt }; } res.json(toUserDTO(await db.users.findById(id)));

Combine with property-level auth: admins see email_verified and last_login_ip, regular users do not.

7. CORS and Security Headers

CORS. The #1 CORS mistake: Access-Control-Allow-Origin: * combined with Access-Control-Allow-Credentials: true. This is rejected by browsers but the misconfig still ships. Correct pattern: explicit allowlist.

const allowedOrigins = ['https://app.example.com', 'https://admin.example.com']; app.use(cors({ origin: (origin, cb) => { if (!origin || allowedOrigins.includes(origin)) cb(null, true); else cb(new Error('Not allowed by CORS')); }, credentials: true }));

Security headers. Use helmet (Node) or equivalent. The essentials:

- Strict-Transport-Security: max-age=63072000; includeSubDomains; preload - X-Content-Type-Options: nosniff - X-Frame-Options: DENY (or CSP frame-ancestors) - Content-Security-Policy: default-src 'self' - Referrer-Policy: strict-origin-when-cross-origin - Permissions-Policy: restrict camera, microphone, geolocation

For JSON APIs, add X-Content-Type-Options: nosniff and set Content-Type: application/json; charset=utf-8 explicitly — prevents MIME confusion attacks.

8. Secrets Management (Not .env Files in Production)

"Just put it in .env" is fine for local dev. In production, secrets belong in a dedicated secrets manager:

- HashiCorp Vault — industry standard, self-hosted or HCP cloud, dynamic secrets - AWS Secrets Manager / Parameter Store — AWS-native, KMS-encrypted, rotation lambdas - Google Secret Manager — GCP-native - Azure Key Vault — Azure-native - Doppler, Infisical — developer-focused SaaS

Why not .env? Checked-in .env files have leaked thousands of credentials to public GitHub (GitGuardian 2024 report: 12.8 million secrets detected). Runtime injection from a secrets manager gives you audit logs, rotation, revocation, and fine-grained access control.

Rotate on a schedule (quarterly for API keys, monthly for DB passwords, immediately on any suspected leak). Use short-lived credentials where possible (IAM roles instead of access keys, Vault dynamic DB credentials, Google Workload Identity).

Scan for leaked secrets in CI: gitleaks, trufflehog, or GitHub's built-in secret scanning. Enable push protection so commits containing secrets are rejected before they ever hit the remote.

9. Dependency and Supply Chain Security

In 2024, the xz-utils backdoor (CVE-2024-3094) nearly planted a nation-state backdoor in every Linux distro. In 2021, the ua-parser-js and node-ipc packages were hijacked. Your dependencies are your attack surface.

Toolchain:

- npm audit / pnpm audit — first line of defense, zero setup - Snyk — free tier for open source, deep vuln database, auto-fix PRs - Dependabot — GitHub-native, automatic PRs for CVEs and version bumps - Renovate — configurable alternative to Dependabot - OWASP Dependency-Check — SCA for Java/Gradle/Maven - Socket.dev — static analysis of npm packages for supply-chain risks - Trivy — container image scanning (also IaC)

Minimum bar:

- Dependabot enabled on every repo - CI fails on high/critical vulnerabilities - Lockfiles (package-lock.json, yarn.lock, poetry.lock) committed - Pin major versions; review auto-PRs before merging - SBOM (Software Bill of Materials) generated per build using Syft or CycloneDX — required for compliance (EU Cyber Resilience Act, US Executive Order 14028)

10. Logging, Monitoring, and Incident Response

You cannot defend what you cannot see. Log the security-relevant events, monitor for anomalies, and have a plan.

What to log (always):

- Authentication: login attempts (success and failure), MFA challenges, logouts - Authorization: 403 denials, role changes, permission grants - Data access: exports, bulk queries, admin actions on user accounts - Configuration changes: feature flags, secret rotations, role assignments

What NOT to log (ever):

- Passwords (even hashed — no reason to have them in logs) - Access tokens, refresh tokens, API keys (redact Authorization headers) - Full credit card numbers, SSNs, PHI (PCI/HIPAA violations) - Decrypted personal data beyond what's needed

Tools:

- Datadog, New Relic, Dynatrace — APM and log aggregation - Sentry — error tracking with built-in PII scrubbing - Elastic / OpenSearch — self-hosted log search - Grafana + Loki — lightweight open source - Panther, Chronicle, Splunk — SIEM for security-specific correlation

Alerts that matter:

- 20+ failed logins from one IP in 5 min — credential stuffing - 50+ 403s from one user in 1 min — IDOR probing - New 5xx error class — potential exploitation - Unusual data export volume — data exfiltration - Authentication from new country for admin account — account takeover

Have a documented incident response runbook: who is on call, what to rotate, how to notify customers (GDPR: 72 hours), who calls the lawyer.

Real-World Example: Hardening a Login Endpoint

Putting it all together. A production-grade POST /login in Express:

import rateLimit from 'express-rate-limit'; import { z } from 'zod'; import bcrypt from 'bcrypt'; import jwt from 'jsonwebtoken';

const LoginSchema = z.object({ email: z.string().email().toLowerCase(), password: z.string().min(1).max(128) });

const loginLimiter = rateLimit({ windowMs: 60 * 1000, max: 5, standardHeaders: true, keyGenerator: (req) => req.ip + ':' + (req.body?.email || '') });

app.post('/login', loginLimiter, async (req, res) => { const parsed = LoginSchema.safeParse(req.body); if (!parsed.success) return res.status(400).json({ error: 'invalid_request' });

const { email, password } = parsed.data; const user = await db.users.findOne({ where: { email } });

// Constant-time: always compute a hash, even on user miss const valid = user && await bcrypt.compare(password, user.passwordHash);

if (!valid) { logger.warn({ email, ip: req.ip }, 'login_failed'); return res.status(401).json({ error: 'invalid_credentials' }); }

const accessToken = jwt.sign({ sub: user.id, role: user.role }, SECRET, { algorithm: 'RS256', expiresIn: '15m', audience: 'api.example.com', issuer: 'auth.example.com' });

res.cookie('refresh', await issueRefreshToken(user.id), { httpOnly: true, secure: true, sameSite: 'strict', maxAge: 7 * 24 * 60 * 60 * 1000 });

logger.info({ userId: user.id, ip: req.ip }, 'login_success'); res.json({ accessToken }); });

This single endpoint implements TLS (at the proxy), validation, rate limiting per IP+email, constant-time authentication, strong hashing, signed tokens with short TTL, httpOnly refresh cookies, and observability. That is the baseline.

Common API Security Mistakes

1. Unauthenticated endpoints leaking data. Every public endpoint must be audited. 2024 Dell and telecom breaches were both this.

2. BOLA — trusting IDs from the client. Always scope queries by the authenticated user's ID/tenant.

3. Mass assignment. Accepting the entire request body into the ORM (User.update(req.body)). Attackers set isAdmin: true. Fix: validate with a DTO/schema.

4. Verbose errors in production. "SQL error in table users" tells an attacker your schema. Return generic errors to clients, log details server-side.

5. CORS wildcard with credentials. A browser misconfiguration that hands cookies to any origin.

6. No rate limiting on login / password reset / signup. Trivial credential stuffing and email enumeration.

7. Long-lived access tokens. 24-hour+ JWTs with no revocation path.

8. Secrets in git. Committed .env, hardcoded API keys. Use gitleaks pre-commit.

9. Outdated dependencies. Enable Dependabot; merge security PRs within 7 days.

10. No monitoring. You only know you were breached when the press calls. Set up anomaly alerts on auth and data access.

Frequently Asked Questions

What is the single most important API security practice?

There isn't one — defense in depth is the point. But if forced to pick: enforce authentication and object-level authorization on every endpoint. Per OWASP, BOLA (API1) is the most common finding and the source of the largest recent breaches.

Is a Web Application Firewall (WAF) enough?

No. A WAF catches known attack patterns (SQLi payloads, common XSS) but cannot protect against business-logic flaws like BOLA, which require application-layer context. WAFs are a useful layer, not a replacement for secure code.

Should I use JWT or session cookies for my API?

JWT when you need stateless verification across multiple services or clients you don't control. Session cookies when you have a traditional monolith and want trivial revocation. For SPAs, a hybrid — short JWT access token in memory + httpOnly refresh-token cookie — is now the standard.

How do I test my API security?

Start with ZAP or Burp Suite for dynamic scanning. Use Semgrep or CodeQL for static analysis of auth patterns. Run Nuclei templates for known CVEs. Hire a pentest annually and after significant changes. For continuous coverage, integrate Snyk, GitHub Advanced Security, or Mend into CI.

What is Zero Trust and does it apply to APIs?

Yes. Zero Trust means every request is authenticated and authorized regardless of network location — there is no "internal" network you can trust. For APIs: mTLS between services, per-request auth, short-lived tokens, and least-privilege IAM on every resource. Google's BeyondCorp and Netflix's Wall-E are canonical implementations.

How long should I keep security logs?

Minimum 90 days for active investigation, 1 year for compliance (PCI DSS, HIPAA, SOC 2). Consider cold storage (S3 Glacier) for 7 years for regulated industries. Encrypt at rest, restrict access with IAM, and hash sensitive fields before storing.

Are API keys still acceptable in 2026?

Yes, for server-to-server contexts with proper controls: bound to specific IPs, rotated quarterly, stored in a secrets manager, scoped to specific endpoints/actions, and monitored for anomalous usage. For user-facing clients (mobile, SPA), use OAuth 2.0 with PKCE instead.

How do I secure GraphQL APIs specifically?

Depth limiting (graphql-depth-limit), query complexity analysis (graphql-cost-analysis), persisted queries in production, field-level authorization, and disabling introspection in production. GraphQL's flexibility amplifies BOLA risk — enforce auth per field and per object, not just per endpoint.

Summary and Next Steps

API security is the sum of many small, disciplined choices — not a single product or checklist item. The 10 practices above are the 80% that blocks 99% of real-world attacks: TLS 1.3, strong auth, object-level authorization, schema validation, rate limiting, minimal responses, strict CORS and security headers, proper secrets management, dependency scanning, and production monitoring.

Audit your top 5 highest-traffic endpoints against this list today. You will find at least one missing practice. Fix it. Move to the next endpoint.

Building or testing an API? Our browser-based developer tools (JSON formatter, hash generator, base64, JWT decoder) are entirely client-side — no data ever leaves your browser, making them safe to use with production payloads:

https://stringtoolsapp.com

Related Tools

- JSON Formatter — prettify and validate API payloads - Base64 Encoder/Decoder — encode/decode auth headers and binary data - Hash Generator — compute HMAC signatures for webhook verification - Password Generator — generate strong API keys and JWT secrets

Explore all tools: https://stringtoolsapp.com