API Security

Last Updated : 1 Aug, 2026

API security protects APIs from unauthorized access and cyber threats using authentication, authorization, encryption, and other security measures.

  • Protects sensitive data and API resources.
  • Secures communication between clients and servers.
  • Controls access to API endpoints.

Importance of API Security

APIs expose application data and functionality, making them a common target for cyberattacks. Implementing API security helps protect business-critical services and maintain trust.

  • Prevents unauthorized access to API endpoints.
  • Reduces the risk of data breaches and cyberattacks.
  • Ensures application availability and reliability.
  • Helps meet security and regulatory compliance requirements.

Common API Security Vulnerabilities

The OWASP API Security identifies the most critical security risks that affect APIs.

1. Broken Object Level Authorization (BOLA)

Occurs when an API does not verify whether a user has permission to access a specific resource.

  • Allows attackers to access another user's data by modifying object identifiers.
  • One of the most common and critical API security vulnerabilities.

2. Broken Authentication

Occurs when authentication mechanisms or token management are implemented incorrectly.

  • Enables attackers to impersonate legitimate users.
  • Results from weak passwords, predictable tokens, or missing MFA.

3. Broken Object Property Level Authorization

Occurs when APIs expose or allow modification of sensitive object properties.

  • Can reveal confidential information.
  • Often caused by excessive data exposure or mass assignment.

4. Unrestricted Resource Consumption

Occurs when an API does not limit resource usage or request frequency.

  • Can lead to denial-of-service (DoS) attacks.
  • Increases infrastructure costs due to excessive requests.

5. Broken Function Level Authorization (BFLA)

Occurs when users can access functions beyond their assigned permissions.

  • May expose administrative operations.
  • Allows privilege escalation.

6. Unrestricted Access to Sensitive Business Flows

Occurs when legitimate business operations can be abused through automation.

  • Can affect bookings, payments, or coupon systems.
  • May result in financial or operational losses.

7. Server-Side Request Forgery (SSRF)

Occurs when an API fetches user-supplied URLs without proper validation.

  • Can expose internal services.
  • May allow access to sensitive infrastructure.

8. Security Misconfiguration

Occurs when APIs use insecure default settings or incorrect configurations.

  • May expose debug information or default credentials.
  • Increases the overall attack surface.

9. Improper Inventory Management

Occurs when outdated or undocumented APIs remain accessible.

  • Makes APIs difficult to monitor and secure.
  • Increases the risk of exploiting forgotten endpoints.

10. Unsafe Consumption of APIs

Occurs when applications trust third-party API responses without validation.

  • Can introduce malicious or invalid data.
  • Increases security risks from external services.

Core API Security Concepts

Core API security concepts help protect APIs from unauthorized access, data breaches, and common security threats.

1. API Inventory and Shadow API Discovery

An API inventory helps organizations track and manage all available APIs, while shadow API discovery identifies undocumented or unmanaged APIs that may introduce security risks.

  • Maintain a centralized inventory of public, private, and internal APIs.
  • Assign an owner to every API for accountability.
  • Keep API documentation synchronized with OpenAPI or Swagger specifications.
  • Deprecate outdated API versions to reduce the attack surface.

Example: Deprecating an API Version

deprecation-headers.js
app.use("/api/v1", (req, res, next) => {
  res.setHeader("Deprecation", "true");
  res.setHeader("Sunset", "Sat, 31 Dec 2026 23:59:59 GMT");
  res.setHeader("Link", '</api/v2>; rel="successor-version"');

  next();
});

2. Transport Layer Security (TLS) and Service-to-Service Trust

Transport Layer Security (TLS) encrypts data exchanged between clients and servers, while mutual TLS (mTLS) authenticates both parties to establish a trusted connection.

  • Use HTTPS for all API communication.
  • Redirect HTTP requests to HTTPS.
  • Use mutual TLS (mTLS) for secure service-to-service communication.

Example: Redirect HTTP Requests to HTTPS

server.js
import express from "express";
import helmet from "helmet";

const app = express();

app.use(
  helmet.hsts({
    maxAge: 63072000,
    includeSubDomains: true,
    preload: true,
  })
);

app.use((req, res, next) => {
  if (req.headers["x-forwarded-proto"] !== "https") {
    return res.redirect(301, `https://${req.headers.host}${req.url}`);
  }

  next();
});
local-tls-server.js
import https from "node:https";
import fs from "node:fs";
import app from "./app.js";

const options = {
  key: fs.readFileSync("./certs/privkey.pem"),
  cert: fs.readFileSync("./certs/fullchain.pem"),
};

https.createServer(options, app).listen(443, () => {
  console.log("TLS server running on port 443");
});

3. OAuth 2.0 for Authentication and Authorization

OAuth 2.0 is an authorization framework that enables applications to access protected resources without exposing user credentials. It uses access tokens to authenticate and authorize API requests.

  • Use the Authorization Code flow with PKCE for web and mobile applications.
  • Grant only the minimum required permissions using scopes.
  • Use short-lived access tokens and securely store refresh tokens.

Example: Exchanging an Authorization Code for an Access Token

token-exchange.js
import * as client from "openid-client";

const config = await client.discovery(
  new URL("https://accounts.google.com"),
  process.env.OAUTH_CLIENT_ID,
  process.env.OAUTH_CLIENT_SECRET
);

const tokens = await client.authorizationCodeGrant(
  config,
  new URL("https://app.example.com/callback"),
  {
    code: req.query.code,
    code_verifier: req.session.codeVerifier,
  }
);

// Access token
console.log(tokens.access_token);

4. Securing APIs with JSON Web Tokens (JWT)

JSON Web Tokens (JWTs) securely transmit user identity and authorization information between clients and servers. After successful authentication, the server issues a signed JWT, which is included in subsequent API requests for verification.

  • Sign JWTs using a strong algorithm such as RS256.
  • Set short expiration times for access tokens.
  • Always verify the token before granting access.

Example: Issuing and Verifying a JWT

jwt.util.js
import jwt from "jsonwebtoken";
import fs from "node:fs";

const privateKey = fs.readFileSync("./keys/private.pem");
const publicKey = fs.readFileSync("./keys/public.pem");

export function issueToken(user) {
  return jwt.sign(
    {
      sub: user.id,
      role: user.role,
      scope: user.scopes.join(" "),
    },
    privateKey,
    {
      algorithm: "RS256",
      expiresIn: "15m",
      issuer: "api.example.com",
    }
  );
}

export function authMiddleware(req, res, next) {
  const header = req.headers.authorization;

  if (!header?.startsWith("Bearer ")) {
    return res.status(401).json({
      error: "Missing bearer token",
    });
  }

  const token = header.split(" ")[1];

  try {
    const payload = jwt.verify(token, publicKey, {
      algorithms: ["RS256"],
      issuer: "api.example.com",
    });

    req.user = {
      id: payload.sub,
      role: payload.role,
      scopes: payload.scope.split(" "),
    };

    next();
  } catch (err) {
    return res.status(401).json({
      error: "Invalid or expired token",
    });
  }
}

5. Role-Based Access Control (RBAC)

Role-Based Access Control (RBAC) restricts API access by assigning permissions to user roles. It ensures that users can perform only the actions allowed for their assigned role.

  • Assign permissions to roles instead of individual users.
  • Enforce authorization using middleware.
  • Follow the principle of least privilege.

Example: Implements RBAC using middleware

rbac.middleware.js
const ROLE_PERMISSIONS = {
  admin: ["read:users", "write:users", "delete:users"],
  editor: ["read:users", "write:users"],
  viewer: ["read:users"],
};

export function requirePermission(permission) {
  return (req, res, next) => {
    const role = req.user?.role;
    const allowed = ROLE_PERMISSIONS[role]?.includes(permission);

    if (!allowed) {
      return res.status(403).json({
        error: "Forbidden: insufficient permissions",
      });
    }

    next();
  };
}
users.routes.js
import { Router } from "express";
import { requirePermission } from "./rbac.middleware.js";

const router = Router();

router.delete(
  "/users/:id",
  requirePermission("delete:users"),
  async (req, res) => {
    // deletion logic here
    res.status(204).send();
  }
);

export default router;

6. Protecting APIs with API Gateways and WAF

An API Gateway centralizes authentication, routing, and security policies, while WAF-style controls protect APIs from malicious requests through rate limiting, input validation, and security headers.

  • Apply rate limiting to prevent abuse.
  • Validate request data before processing.
  • Configure security headers for API responses.

Example: Protecting APIs Using Security Middleware

security-middleware.js
import helmet from "helmet";
import rateLimit from "express-rate-limit";
import { z } from "zod";

export const securityHeaders = helmet();

export const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  limit: 100, // 100 requests per IP
  standardHeaders: "draft-8",
  legacyHeaders: false,
  message: {
    error: "Too many requests, please try again later.",
  },
});

const createOrderSchema = z.object({
  productId: z.string().uuid(),
  quantity: z.number().int().positive().max(100),
});

export function validateBody(schema) {
  return (req, res, next) => {
    const result = schema.safeParse(req.body);

    if (!result.success) {
      return res.status(400).json({
        error: result.error.flatten(),
      });
    }

    req.body = result.data;
    next();
  };
}

export { createOrderSchema };
app.js
import express from "express";
import {
  securityHeaders,
  apiLimiter,
  validateBody,
  createOrderSchema,
} from "./security-middleware.js";

const app = express();

app.use(express.json({ limit: "10kb" }));
app.use(securityHeaders);
app.use("/api", apiLimiter);

app.post(
  "/api/orders",
  validateBody(createOrderSchema),
  (req, res) => {
    res.status(201).json({
      status: "created",
      order: req.body,
    });
  }
);

7. Preventing SSRF and Validating Third-Party API Responses

Server-Side Request Forgery (SSRF) occurs when an API fetches user-supplied URLs without proper validation. Validating third-party API responses helps prevent malicious or unexpected data from affecting your application.

  • Allow requests only to trusted domains.
  • Block requests to internal or private IP addresses.
  • Validate and sanitize all third-party API responses.

Example: Blocking SSRF Requests

JavaScript
import { isIP } from "node:net";

const BLOCKED_RANGES = [
  "127.",
  "10.",
  "169.254.",
  "192.168.",
];

const ALLOWED_HOSTS = [
  "api.example.com",
  "images.example.com",
];

export function assertSafeUrl(rawUrl) {
  const url = new URL(rawUrl);

  const isBlocked = BLOCKED_RANGES.some((prefix) =>
    url.hostname.startsWith(prefix)
  );

  if (
    isBlocked ||
    (isIP(url.hostname) === 0 &&
      !ALLOWED_HOSTS.includes(url.hostname))
  ) {
    throw new Error("Blocked: destination not allowed");
  }
}

8. Secrets Management

Secrets management securely stores and manages sensitive information such as API keys, database credentials, and encryption keys. It helps prevent unauthorized access and reduces the risk of credential exposure.

  • Store secrets in a dedicated secrets manager.
  • Encrypt secrets both at rest and in transit.
  • Rotate secrets regularly and grant least-privilege access.

Example: Retrieving Secrets from AWS Secrets Manager

secrets-client.js
import {
  SecretsManagerClient,
  GetSecretValueCommand,
} from "@aws-sdk/client-secrets-manager";

const client = new SecretsManagerClient({
  region: "us-east-1",
});

export async function getSecret(secretId) {
  const command = new GetSecretValueCommand({
    SecretId: secretId,
  });

  const response = await client.send(command);

  return JSON.parse(response.SecretString);
}

9. Testing for Broken Object Level Authorization (BOLA)

Testing for Broken Object Level Authorization (BOLA) verifies that users can access only the resources they are authorized to view or modify. It helps identify unauthorized access caused by missing object-level authorization checks.

  • Test object access using multiple user accounts.
  • Verify authorization for all ID-based API endpoints.
  • Ensure expired or revoked tokens are rejected.

Example: Testing BOLA

bola-test.spec.js
test("user A cannot read user B's order via ID substitution", async () => {
  const res = await request(app)
    .get(`/api/orders/${userBOrderId}`)
    .set("Authorization", `Bearer ${userAToken}`);

  expect(res.status).toBe(403);
});

10. Regular Security Audits and Penetration Testing

Regular security audits and penetration testing help identify vulnerabilities before they can be exploited. They ensure APIs remain secure as applications evolve.

  • Perform vulnerability scans regularly.
  • Conduct penetration testing after major changes.
  • Track and remediate identified security issues.

Example: Running Security Audits

CI Pipeline

The following command scans project dependencies for high-severity vulnerabilities:

npm audit --audit-level=high

Scheduled ZAP Scan

The following command performs an automated API security scan using OWASP ZAP:

docker run -t owasp/zap2docker-stable zap-api-scan.py \
-t https://api.example.com/openapi.json \
-f openapi \
-r zap-report.html

11. Logging, Monitoring, and Incident Response

Logging and monitoring help detect suspicious API activity, while an incident response plan enables organizations to respond quickly to security incidents and minimize their impact.

  • Log API requests without exposing sensitive information.
  • Monitor API activity for unusual behavior.
  • Maintain an incident response plan for security events.

Example: Logging API Requests

logger.js
import pino from "pino";

export const logger = pino({
  level: process.env.LOG_LEVEL || "info",
  redact: [
    "req.headers.authorization",
    "req.body.password",
  ],
});
audit-log.middleware.js
import { logger } from "./logger.js";

export function auditLog(req, res, next) {
  const start = Date.now();

  res.on("finish", () => {
    logger.info(
      {
        method: req.method,
        path: req.originalUrl,
        status: res.statusCode,
        userId: req.user?.id ?? "anonymous",
        ip: req.ip,
        durationMs: Date.now() - start,
      },
      "api_request"
    );
  });

  next();
}
Comment