REST API Security Best Practices

A comprehensive guide to securing your REST API from authentication to OWASP Top 10

Last updated:

Why API Security Matters

APIs are the #1 attack surface for modern applications. Every API endpoint is a potential entry point for attackers. A single security vulnerability can expose sensitive user data, enable account takeovers, and result in catastrophic breaches.

🎯

APIs Are the Primary Target

Over 90% of web applications use APIs. Attackers specifically target APIs because they expose business logic and data directly, often with less protection than traditional web interfaces.

πŸ’Έ

Cost of an API Breach

The average cost of a data breach exceeds $4 million. API breaches often expose bulk data β€” not just one record, but potentially millions of user accounts in a single attack.

πŸ“‹

OWASP API Security Top 10

The Open Web Application Security Project maintains a dedicated API Security Top 10 list (2023). Understanding these risks is the foundation of API security.

βš–οΈ

Compliance Requirements

GDPR, HIPAA, PCI-DSS, and SOC 2 all require robust API security controls. Failure to comply can result in significant fines and legal liability.

Always Use HTTPS

HTTPS is non-negotiable for production APIs. Never serve API endpoints over plain HTTP β€” it exposes tokens, credentials, and data to network eavesdropping.

πŸ”’ Enforce HTTPS Everywhere

βœ… Good Strict-Transport-Security: max-age=31536000; includeSubDomains
❌ Bad http://api.example.com/users

The HSTS header forces browsers to use HTTPS for your domain for one year. Enforce HTTPS at the infrastructure level (load balancer/CDN), not just application level.

πŸ“œ TLS Configuration

βœ… Minimum TLS 1.2 (acceptable), TLS 1.3 (recommended)
❌ Disable TLS 1.0, TLS 1.1, SSL 2/3 β€” all vulnerable

Use TLS 1.3 for new deployments. Disable older protocol versions. Rotate TLS certificates automatically using Let's Encrypt or your cloud provider's certificate manager.

Authentication: Verify Who Is Calling

Authentication verifies the identity of the caller. See our full Authentication guide for implementation details. Here are the security rules that apply regardless of method.

API Keys

Simple but Limited

Long-lived static credentials. Easy to implement but carry risks: no expiry by default, easy to leak via version control or logs.

Authorization: Bearer api_key_xyz789abc
JWT

Stateless & Expirable

Self-contained tokens with expiry. Verifiable without database lookups. Use short-lived access tokens (15 min–1 hour) with refresh token rotation.

Authorization: Bearer eyJhbGci...
OAuth 2.0

Industry Standard

Delegated authorization framework. Best for third-party integrations. Never implement your own OAuth β€” use a proven identity provider.

scope: read:users write:orders

Authentication Security Rules

🚫 Never Put Keys in URLs

❌ Dangerous GET /api/users?api_key=secret123
βœ… Safe Authorization: Bearer secret123

URLs are logged by web servers, proxies, and browser history. Any credential in a URL is immediately compromised.

⏰ Short Token Expiry

βœ… Access token expires_in: 900 (15 minutes)
βœ… Refresh token expires_in: 2592000 (30 days)

Short-lived access tokens limit the damage of a stolen token. Refresh tokens should rotate on each use.

πŸ” Secrets Management

❌ Never const secret = "hardcoded_secret";
βœ… Always process.env.JWT_SECRET

Store secrets in environment variables or a secrets manager (AWS Secrets Manager, HashiCorp Vault). Never commit secrets to version control.

Authorization: Verify What They Can Do

Authentication confirms who you are. Authorization controls what you can do. Both must be checked on every request.

Role-Based Access Control (RBAC)

Assign permissions to roles, then assign roles to users. Simpler to manage than per-user permissions.

  • admin: full read/write access to all resources
  • editor: can create and update, cannot delete
  • viewer: read-only access

Principle of Least Privilege

Grant only the minimum permissions required. An API key for reading product listings should not have permission to delete users.

GET /api/v1/users/456/orders
Authorization: Bearer <token>

// Server MUST verify:
// 1. Token is valid βœ“ (authentication)
// 2. Token belongs to user 456
//    OR caller has admin role βœ“ (authorization)
//
// If user 123 requests user 456's orders:
HTTP/1.1 403 Forbidden
{
  "error": {
    "code": "FORBIDDEN",
    "message": "Access denied"
  }
}
⚠️

The BOLA/IDOR Vulnerability

Broken Object Level Authorization (BOLA), also known as Insecure Direct Object Reference (IDOR), is the #1 API vulnerability. It occurs when an API returns any resource by ID without checking ownership.

❌ Vulnerable: No ownership check

// Returns ANY user's data for any valid ID
app.get('/api/orders/:id', async (req, res) => {
  const order = await Order.findById(req.params.id);
  res.json(order); // BUG: no ownership check!
});

βœ… Secure: Ownership verification

app.get('/api/orders/:id', authenticate, async (req, res) => {
  const order = await Order.findOne({
    _id: req.params.id,
    userId: req.user.id // REQUIRED: tie to authenticated user
  });
  if (!order) return res.status(404).json({error: 'NOT_FOUND'});
  res.json(order);
});

Input Validation & Injection Prevention

Never trust client-provided data. Validate every input on the server side β€” type, length, format, and range. Client-side validation is UX; server-side validation is security.

πŸ” Validate All Input

βœ… Validated age: integer, min: 0, max: 150
❌ Unvalidated age: "'; DROP TABLE users; --"

Use a validation library (Joi, Zod, class-validator) to define strict schemas. Reject requests that don't conform to the expected structure.

πŸ›‘οΈ SQL Injection Prevention

❌ Vulnerable WHERE name = '${req.body.name}'
βœ… Safe (parameterized) WHERE name = $1, [req.body.name]

Always use parameterized queries or an ORM. Never concatenate user input into SQL strings.

πŸ“‹ Content-Type Validation

βœ… Check Content-Type: application/json

Reject requests with unexpected Content-Type headers. This prevents attackers from sending malicious payloads disguised as valid requests.

πŸ“ Limit Payload Size

βœ… Express example express.json({ limit: '100kb' })

Set strict limits on request body size. Without limits, attackers can send gigabyte payloads to exhaust memory.

Rate Limiting & Throttling

Rate limiting is security, not just performance. Without it, attackers can brute-force passwords, enumerate resources, and perform DDoS attacks against your API.

  • Apply rate limits per authenticated user, per IP, and globally
  • Stricter limits on authentication endpoints (login, password reset)
  • Return 429 Too Many Requests with Retry-After header
  • Implement at API gateway level, not just application code
  • Different rate limits for different tiers (free vs paid)

See our comprehensive Rate Limiting guide for implementation details and algorithms.

429 Too Many Requests
HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0

{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many requests",
    "retry_after": 60
  }
}

CORS Configuration

Cross-Origin Resource Sharing (CORS) controls which domains can make browser requests to your API. Misconfigured CORS is a common security vulnerability.

βœ… Allowlist Specific Origins

βœ… Secure Access-Control-Allow-Origin: https://app.example.com
❌ Dangerous with credentials Access-Control-Allow-Origin: * Access-Control-Allow-Credentials: true

Using * with credentials allows any website to make authenticated requests as your user. Always allowlist specific origins in production.

πŸ” Validate Origin Server-Side

βœ… Dynamic allowlist const allowed = ['https://app.example.com', 'https://www.example.com'];

Check the Origin header against a server-side allowlist. Reflect the origin back only if it's in the allowlist β€” never blindly reflect any origin.

Sensitive Data Protection

πŸ“

Never Log Request Bodies

Request bodies may contain passwords, tokens, credit card numbers, and PII. Log request metadata (method, path, status code, timing) but never the body content.

🎭

Mask Sensitive Fields

Partially mask sensitive data in API responses: "card_number": "****1234". Never return full card numbers, SSNs, or passwords in responses.

πŸ“Š

Paginate Responses

Never return unlimited records from any endpoint. Always enforce pagination limits to prevent bulk data extraction attacks.

πŸ”—

No PII in URLs

Avoid /users?ssn=123-45-6789 β€” URLs are logged everywhere. Put sensitive identifiers in the request body or use opaque tokens.

OWASP API Security Top 10 (2023)

The OWASP API Security Top 10 is the definitive reference for API vulnerabilities. Every API developer should be familiar with all 10 risks.

API1 Broken Object Level Authorization (BOLA) β€” Access other users' objects by manipulating IDs
API2 Broken Authentication β€” Weak auth mechanisms, missing token validation
API3 Broken Object Property Level Authorization β€” Exposing or modifying unauthorized object properties
API4 Unrestricted Resource Consumption β€” No rate limits, no pagination, DoS via large requests
API5 Broken Function Level Authorization β€” Regular users accessing admin functions
API6 Unrestricted Access to Sensitive Business Flows β€” Buying out-of-stock items, mass scraping
API7 Server Side Request Forgery (SSRF) β€” API fetches attacker-controlled URLs, reaching internal services
API8 Security Misconfiguration β€” Verbose errors, open CORS, default credentials, debug mode on
API9 Improper Inventory Management β€” Forgotten dev/staging APIs exposed to the internet
API10 Unsafe Consumption of APIs β€” Trusting third-party API responses without validation

Security Headers

Set these HTTP response headers to protect against common browser-based attacks. These are in addition to authentication and authorization controls.

Header Recommended Value Purpose
Strict-Transport-Security max-age=31536000; includeSubDomains Force HTTPS for 1 year
X-Content-Type-Options nosniff Prevent MIME type sniffing
X-Frame-Options DENY Prevent clickjacking
Content-Security-Policy default-src 'self' Prevent XSS attacks
Referrer-Policy strict-origin-when-cross-origin Limit referrer header leakage
Permissions-Policy geolocation=(), camera=(), microphone=() Disable unused browser features

API Security Checklist

Use this checklist before deploying any API to production. Every item checked is a class of attacks prevented.

πŸ”’ Transport Security

  • HTTPS enforced with HSTS header
  • TLS 1.2 minimum, TLS 1.3 preferred
  • HTTP requests redirected to HTTPS
  • Valid, up-to-date TLS certificate

πŸ” Authentication

  • All non-public endpoints require auth
  • API keys/tokens not in URLs or source code
  • Short expiry on access tokens
  • Refresh tokens rotate on use

πŸ›‘οΈ Authorization

  • Resource ownership checked on every request
  • No BOLA/IDOR vulnerabilities
  • Admin functions protected by role checks
  • Principle of least privilege applied

βœ… Input Validation

  • All inputs validated server-side
  • Parameterized queries (no SQL injection)
  • Request body size limits enforced
  • Content-Type header validated

🚦 Rate Limiting

  • Rate limits on all endpoints
  • Stricter limits on auth endpoints
  • 429 with Retry-After header
  • Implemented at gateway level

πŸ“Š Data Protection

  • No sensitive data in logs
  • Sensitive fields masked in responses
  • Pagination enforced on all list endpoints
  • PII not exposed in URLs

Frequently Asked Questions

What is the most common REST API security vulnerability?

Broken Object Level Authorization (BOLA/IDOR) is the #1 vulnerability per OWASP API Security Top 10. It happens when an API returns data for any ID without verifying the requesting user has permission. Fix: always cross-reference the requested resource ID with the authenticated user's identity.

How do I prevent IDOR (Insecure Direct Object Reference) attacks?

Always include the authenticated user's ID in database queries, not just the resource ID. For example: WHERE id = $1 AND user_id = $2. Additionally, consider using UUIDs instead of sequential integers to make IDs unpredictable.

Should I use JWT or sessions for REST API authentication?

JWT is generally preferred for REST APIs because it is stateless β€” no server-side session storage needed. Use short-lived access tokens (15 min to 1 hour) with refresh token rotation. However, JWTs cannot be invalidated before expiry without additional infrastructure, so sessions may be better for high-security applications that require instant logout.

How do I test my REST API for security vulnerabilities?

Use a combination of automated and manual testing: OWASP ZAP for automated vulnerability scanning, Burp Suite for manual penetration testing, and dedicated API security scanners. Also see our API Testing guide for comprehensive testing strategies. Always test your authentication bypass, IDOR, and injection vulnerability scenarios.