This article was originally published on AI Study Room. For the full version with working code examples and related articles, visit the original post.
API Security Best Practices 2026: JWT, Rate Limiting, Input Validation, and OWASP for APIs
APIs are the front door to your application — and the #1 attack surface in 2026. This guide covers the security practices every API developer must implement, from authentication to rate limiting to input validation, with concrete code examples.
1. Authentication: JWT Done Right
JWTs are ubiquitous, but most implementations are vulnerable. Here are the rules: always set an expiration (exp) claim — never issue eternal tokens; always validate the iss (issuer) and aud (audience) claims — don't accept tokens issued for other services; never accept alg: none — explicitly whitelist your signing algorithm; use RS256 or ES256, not HS256 with a weak secret; store refresh tokens in an httpOnly, Secure, SameSite=Strict cookie, never in localStorage.
const jwt = require('jsonwebtoken');
function createToken(user) {
return jwt.sign(
{ sub: user.id, role: user.role },
process.env.JWT_PRIVATE_KEY,
{ algorithm: 'RS256', expiresIn: '15m', issuer: 'api.example.com', audience: 'app.example.com' }
);
}
function verifyToken(token) {
return jwt.verify(token, process.env.JWT_PUBLIC_KEY, {
algorithms: ['RS256'],
issuer: 'api.example.com',
audience: 'app.example.com'
});
}
- Authorization: RBAC and ABAC
Never trust the client to enforce authorization. Every API endpoint must verify: is this user authenticated? Does this user have permission for this action on this resource? Implement role-based access control (RBAC) for simple cases: admin, editor, viewer. For complex cases, use attribute-based access control (ABAC): "Can this user edit this document if the document is in draft status and the user is in the same department?"
function authorize(user, action, resource) {
if (user.role === 'admin') return true;
if (action === 'read' && resource.public) return true;
if (action === 'write' && resource.ownerId === user.id) return true;
if (action === 'write' && resource.departmentId === user.departmentId && user.role === 'editor') return true;
return false;
}
- Rate Limiting: Stop Abuse Before It Starts
Every public API endpoint needs rate limiting. Without it, a single misconfigured client can take down your service. Use the token bucket or sliding window algorithm — fixed window is too bursty. Rate limit by: IP address (basic), API key (better), user ID + endpoint (best). Return standard headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, and Retry-After when throttled. Return HTTP 429 (Too Many Requests), not 200 with an error body.
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const limiter = rateLimit({
store: new RedisStore({ client: redisClient }),
windowMs: 60 * 1000,
max: 100, // 100 requests per minute
standardHeaders: true,
legacyHeaders: false,
keyGenerator: (req) => req.user?.id || req.ip,
handler: (req, res) => {
res.status(429).json({
error: 'Too many requests. Retry after 60 seconds.',
retryAfter: 60
});
}
});
- Input Validation: Never Trust the Client
The #1 cause of API vulnerabilities is trusting user input. Validate everything: type (is this a string? number?), format (is this a valid email? UUID?), length (is this under the max?), range (is this number between 1 and 100?), and business rules (is this status transition allowed?). Use a schema validation library — never write validation by hand. Zod (TypeScript), Pydantic (Python), or Joi (Node.js) — pick one and use it on every endpoint.
import { z } from 'zod';
const CreateUserSchema = z.object({
email: z.string().email().max(255),
name: z.string().min(1).max(100).regex(/^[a-zA-Z\s-]+$/),
age: z.number().int().min(13).max(120),
role: z.enum(['user', 'editor', 'admin']),
website: z.string().url().optional()
});
function createUser(req, res) {
const result = CreateUserSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({
error: 'Validation failed',
details: result.error.flatten().fieldErrors
});
}
// result.data is now guaranteed valid
}
- CORS: Be Strict, Not Permissive
Never use Access-Control-Allow-Origin: * on an API that uses cookies or tokens. Specify exact origins. Never echo back the Origin header without whitelisting. Don't allow Access-Control-Allow-Credentials: true with a wildcard origin. For public APIs that legitimately need broad access, use API keys (in headers) rather than cookies, so CORS isn't the security boundary.
6. SQL Injection: Still Relevant in 2026
Parameterized queries eliminate SQL in
Read the full article on AI Study Room for complete code examples, comparison tables, and related resources.
Found this useful? Check out more developer guides and tool comparisons on AI Study Room.
Top comments (0)