aicalcus.com
Tech & Developer4 min read

API Security Best Practices in 2025: What Every Developer Needs to Implement

API attacks account for 34% of all web application attacks in 2025. The most common vulnerabilities are all preventable. Here's the implementation checklist.

AMAlex Morgan·
API Security Best Practices in 2025: What Every Developer Needs to Implement

Gartner predicted API vulnerabilities would become the most common attack vector by 2022. By 2025, that prediction has materialized — API attacks drive 34% of all web application security incidents. Most of these attacks exploit well-documented, preventable vulnerabilities.

The OWASP API Security Top 10 (2023 Edition)

OWASP's API-specific vulnerability list is the starting point for any API security program:

RankVulnerabilityBrief
API1Broken Object Level AuthorizationAccess to other users' objects
API2Broken AuthenticationWeak auth mechanisms
API3Broken Object Property Level AuthorizationMass assignment, excessive exposure
API4Unrestricted Resource ConsumptionNo rate limiting
API5Broken Function Level AuthorizationAdmin functions exposed
API6Unrestricted Access to Sensitive Business FlowsScraping, abuse of critical flows
API7Server Side Request ForgerySSRF attacks
API8Security MisconfigurationMissing headers, verbose errors
API9Improper Inventory ManagementShadow APIs, deprecated endpoints
API10Unsafe Consumption of APIsTrusting external API responses

Implementation Checklist

Authentication

OAuth 2.0 / JWT:

// Verify JWT on every request
import jwt from 'jsonwebtoken';

function authenticateRequest(req, res, next) {
  const token = req.headers.authorization?.replace('Bearer ', '');
  if (!token) return res.status(401).json({ error: 'No token provided' });
  
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET, {
      algorithms: ['HS256'], // Specify allowed algorithms explicitly
      expiresIn: '24h'
    });
    req.user = decoded;
    next();
  } catch (err) {
    return res.status(401).json({ error: 'Invalid token' });
  }
}

API Key rotation:

  • Rotate API keys every 90 days (or on suspected compromise)
  • Never embed API keys in client-side code
  • Use environment variables, not config files committed to git

Authorization (BOLA Prevention — Most Common Vulnerability)

The #1 API vulnerability: users accessing other users' data via object ID manipulation.

Wrong (vulnerable):

// Anyone can access any user's data by changing the ID
app.get('/api/users/:id/data', authenticate, async (req, res) => {
  const data = await getData(req.params.id);
  res.json(data);
});

Correct:

// Verify the authenticated user owns the requested resource
app.get('/api/users/:id/data', authenticate, async (req, res) => {
  if (req.user.id !== req.params.id && !req.user.isAdmin) {
    return res.status(403).json({ error: 'Forbidden' });
  }
  const data = await getData(req.params.id);
  res.json(data);
});

Rate Limiting (API4)

Essential for all APIs — prevents abuse, DoS, and scraping:

import rateLimit from 'express-rate-limit';

const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // 100 requests per window
  standardHeaders: true,
  legacyHeaders: false,
  keyGenerator: (req) => req.user?.id || req.ip, // Per-user, not per-IP
});

// Stricter for auth endpoints
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 10,
  message: 'Too many auth attempts'
});

app.use('/api/', apiLimiter);
app.use('/api/auth/', authLimiter);

Input Validation

Validate all input — don't trust client-submitted data:

import { z } from 'zod';

const createUserSchema = z.object({
  email: z.string().email().max(255),
  name: z.string().min(1).max(100),
  role: z.enum(['user', 'editor']), // Never accept 'admin' from client
});

app.post('/api/users', authenticate, async (req, res) => {
  const parsed = createUserSchema.safeParse(req.body);
  if (!parsed.success) {
    return res.status(400).json({ error: parsed.error.format() });
  }
  // Use parsed.data, never req.body directly
});

Security Headers

Every API response should include:

app.use((req, res, next) => {
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('X-Frame-Options', 'DENY');
  res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
  res.setHeader('Content-Security-Policy', "default-src 'none'");
  res.removeHeader('X-Powered-By'); // Don't leak framework info
  next();
});

Error Handling (Don't Leak Information)

Wrong (leaks stack trace, framework info, DB schema):

app.get('/api/data', async (req, res) => {
  const data = await db.query(sql);
  res.json(data);
  // On error: sends full stack trace to client
});

Correct:

app.get('/api/data', async (req, res) => {
  try {
    const data = await db.query(sql);
    res.json(data);
  } catch (err) {
    console.error('DB error:', err); // Log full error server-side
    res.status(500).json({ error: 'Internal server error' }); // Generic message to client
  }
});

API Security Testing

Before shipping, test with:

ToolPurposeCost
OWASP ZAPActive vulnerability scanningFree
Postman (Security)API security test collectionsFree/paid
Burp SuiteProfessional pen testing$449/year
SemgrepStatic analysis, secrets scanningFree (OSS)
SnykDependency vulnerability scanningFree tier available

Automated testing catches the most common vulnerabilities. Manual penetration testing should be done before major launches.

Use the Cloud Cost Calculator to estimate the infrastructure cost of adding WAF (Web Application Firewall) protection to your APIs.

Get weekly AI cost benchmarks & productivity data

Join 4,200+ founders, developers, and creators. No spam, unsubscribe anytime.

#api-security#security#developer#authentication#rate-limiting