DEV Community

ZNY
ZNY

Posted on

The Complete Guide to API Security Best Practices in 2026

The Complete Guide to API Security Best Practices in 2026

APIs are the front door to your data. A single vulnerability can expose millions of records. Here's how to lock it down.

Authentication: JWT vs OAuth2 vs API Keys

JWT — Good for Stateless Auth

// Sign
const jwt = require('jsonwebtoken');
const token = jwt.sign(
  { userId: user.id, role: user.role },
  process.env.JWT_SECRET,
  { expiresIn: '1h', issuer: 'your-api' }
);

// Verify middleware
function verifyToken(req, res, next) {
  const auth = req.headers.authorization;
  if (!auth?.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'No token' });
  }
  try {
    req.user = jwt.verify(auth.slice(7), process.env.JWT_SECRET);
    next();
  } catch (e) {
    return res.status(401).json({ error: 'Invalid token' });
  }
}
Enter fullscreen mode Exit fullscreen mode

Store secrets securely: Use environment variables, never in code.

OAuth2 for Third-Party Access

// Authorization Code flow with PKCE
const crypto = require('crypto');

function generatePKCE() {
  const verifier = crypto.randomBytes(32).toString('base64url');
  const challenge = crypto
    .createHash('sha256')
    .update(verifier)
    .digest('base64url');
  return { verifier, challenge };
}

const { verifier, challenge } = generatePKCE();
// Redirect user to OAuth provider with challenge
Enter fullscreen mode Exit fullscreen mode

Rate Limiting

const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100,
  standardHeaders: true,
  legacyHeaders: false,
  keyGenerator: (req) => req.ip,
  handler: (req, res) => {
    res.status(429).json({
      error: 'Too many requests',
      retryAfter: Math.ceil(15 * 60 * (req.rateLimit.remaining / 100))
    });
  }
});

app.use('/api/', limiter);
Enter fullscreen mode Exit fullscreen mode

Input Validation — Never Trust User Input

const { z } = require('zod');

const userSchema = z.object({
  email: z.string().email().max(255),
  age: z.number().int().min(13).max(120),
  role: z.enum(['user', 'admin']).default('user')
});

app.post('/users', async (req, res) => {
  const result = userSchema.safeParse(req.body);
  if (!result.success) {
    return res.status(400).json({
      error: 'Validation failed',
      details: result.error.flatten()
    });
  }
  // Proceed with result.data
});
Enter fullscreen mode Exit fullscreen mode

CORS: Lock It Down

const cors = require('cors');

app.use(cors({
  origin: ['https://yourdomain.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
  maxAge: 86400
}));
Enter fullscreen mode Exit fullscreen mode

Security Headers

const helmet = require('helmet');
app.use(helmet());
// Adds: Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, etc.
Enter fullscreen mode Exit fullscreen mode

Audit Logging

function auditLog(action, userId, resource, details) {
  console.log(JSON.stringify({
    timestamp: new Date().toISOString(),
    action, userId, resource, details,
    ip: req.ip
  }));
}

// Log every mutation
app.post('/api/users', async (req, res) => {
  auditLog('CREATE_USER', req.user.id, 'users', req.body);
  // ...
});
Enter fullscreen mode Exit fullscreen mode

Conclusion

API security is layers: auth, rate limiting, input validation, CORS, headers, and audit logs. Implement all of them. A breach costs far more than the development time.

Protect your API with enterprise-grade security — built-in auth, rate limiting, and monitoring on a single platform.

Top comments (0)