← All posts
next.jsapi securityvibe codingsecurity auditAI-built apps

Next.js API Route Security Audit: A Developer's Workflow

O

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:

  • [ ] Authentication: Are protected routes properly checking user identity (e.g., with NextAuth.js or a JWT middleware)?
  • [ ] Authorization: Does the route verify the user has permission to perform the action (e.g., is the user the owner of the resource)?
  • [ ] Input validation: Are request parameters, body, and query strings validated and sanitized to prevent injection attacks?
  • [ ] Error handling: Are error messages generic and not leaking stack traces or database details?
  • [ ] Rate limiting: Is there at least basic rate limiting to prevent abuse?
  • [ ] Secrets: Are API keys, database URLs, or tokens hardcoded or exposed in client-side code?
  • [ ] CORS: Are CORS headers restrictively configured, not allowing all origins?
  • [ ] Dependencies: Are there known CVEs in packages used by the 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" | sort

    2. 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:

  • Missing authentication on GET endpoints: Developers assume only POST routes need auth, but GET routes often expose sensitive data.
  • Using `req.query` without validation: AI models may generate code that directly uses query parameters in database calls, leading to SQL injection.
  • Hardcoded secrets: AI tools sometimes inline API keys in route files. Always move them to .env.local.
  • Overly permissive CORS: Setting Access-Control-Allow-Origin: * for simplicity.
  • No rate limiting: AI-generated apps often skip rate limiting entirely, making them vulnerable to brute force or DDoS.
  • Verbose error responses: Returning full error objects that include stack traces or database schemas.
  • Ignoring HTTP method enforcement: Not checking 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.

    Is your app secure?

    Free scan in 30 seconds. No signup needed.

    Scan My App Free