Next.js API Route Security Audit: A Developer's Workflow
OverMCP Team
Quick answer
A next.js api route security audit involves systematically reviewing each API endpoint for authentication, authorization, input validation, rate limiting, and secret exposure. For AI-built apps, common vulnerabilities include missing authentication checks, SQL injection, and hardcoded secrets. Use a combination of manual code review and automated scanning tools to catch issues before deployment.
What to check first
Before diving deep, run this quick checklist on every API route:
Step-by-step fix
Follow this workflow to audit and fix your Next.js API routes.
1. Map all API routes
List all files under pages/api/ or app/api/ (App Router). Use a simple script or grep:
find pages/api -name "*.ts" -o -name "*.js" | sort2. Review authentication and authorization
For each route, check if authentication is enforced. Example of a vulnerable route:
// pages/api/user/profile.ts
import type { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
// No auth check! Anyone can access any user's data.
const { userId } = req.query;
const user = await db.findUser(userId);
res.status(200).json(user);
}Fix by adding a session check and verifying ownership:
import { getSession } from 'next-auth/react';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const session = await getSession({ req });
if (!session) return res.status(401).json({ error: 'Unauthorized' });
const { userId } = req.query;
// Authorization: user can only access own profile
if (session.user.id !== userId) return res.status(403).json({ error: 'Forbidden' });
const user = await db.findUser(userId);
res.status(200).json(user);
}3. Validate and sanitize inputs
Never trust user input. Use a library like zod for validation:
import { z } from 'zod';
const schema = z.object({
email: z.string().email(),
name: z.string().min(1).max(100),
});
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') return res.status(405).end();
const result = schema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ error: 'Invalid input', details: result.error });
}
// Use sanitized data
const { email, name } = result.data;
// ...
}4. Secure error messages
Avoid exposing internal details:
// Bad: exposes database error
catch (error) {
res.status(500).json({ error: error.message });
}
// Good: generic message, log internally
catch (error) {
console.error('Database error:', error);
res.status(500).json({ error: 'Internal server error' });
}5. Add rate limiting
Use a simple in-memory rate limiter or a package like express-rate-limit (wrapped for Next.js):
import rateLimit from 'express-rate-limit';
import slowDown from 'express-slow-down';
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
});
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
await new Promise((resolve, reject) => {
limiter(req as any, res as any, (result) => {
if (result instanceof Error) reject(result);
else resolve(result);
});
});
// ...
}6. Scan for leaked secrets
Use a secret leak scanner to find hardcoded keys in your codebase. For example, never do this:
const apiKey = 'sk-1234567890abcdef'; // exposed!Instead, use environment variables and validate they exist:
const apiKey = process.env.OPENAI_API_KEY;
if (!apiKey) throw new Error('Missing OPENAI_API_KEY');7. Check CORS configuration
Restrict origins in your API route or a global middleware:
// pages/api/[...route].ts
import Cors from 'cors';
const cors = Cors({
origin: ['https://yourdomain.com'], // not '*'
methods: ['GET', 'POST'],
});
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
await new Promise((resolve, reject) => {
cors(req as any, res as any, (result) => {
if (result instanceof Error) reject(result);
else resolve(result);
});
});
// ...
}8. Audit dependencies
Run npm audit to find known vulnerabilities. Use a free AI app security scanner for continuous monitoring.
Common mistakes
AI-built apps often make these errors:
.env.local.Access-Control-Allow-Origin: * for simplicity.req.method and allowing unintended PUT/DELETE operations.For a deeper dive, check out our Vercel security scanner to automate route audits.
FAQ
What is the most common vulnerability in Next.js API routes?
The most common is missing authentication and authorization checks, especially on GET endpoints that return user data. AI-generated code often assumes all routes are protected.
How often should I run a next.js api route security audit?
Run a full audit before every production deployment. For continuous safety, use continuous security monitoring to catch new vulnerabilities as code changes.
Can I automate the audit process?
Yes. Use CI/CD pipelines with tools like npm audit for dependencies, a security headers checker for response headers, and secret scanners to detect exposed keys. Automated scanning complements manual review.