Step-by-Step Guide
6 steps

How to Secure API Endpoints

Every API endpoint is a potential attack surface. Unprotected endpoints can leak data, allow unauthorized modifications, and exhaust resources. This guide covers the essential security measures every API endpoint needs.

Find security issues automatically before attackers do.

Follow These Steps

1

Add authentication to every protected endpoint

Verify the user is authenticated before processing any request that accesses or modifies data.

Code Example
// Next.js API route
import { auth } from '@/auth'

export async function GET(req: Request) {
  const session = await auth()
  if (!session?.user) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 })
  }
  // Process authenticated request
}

// Express.js middleware
function requireAuth(req, res, next) {
  const token = req.headers.authorization?.replace('Bearer ', '')
  if (!token) return res.status(401).json({ error: 'Unauthorized' })
  try {
    req.user = verifyToken(token)
    next()
  } catch {
    res.status(401).json({ error: 'Invalid token' })
  }
}
2

Add authorization checks

After authentication, verify the user has permission to access the specific resource.

Code Example
// Check resource ownership
export async function DELETE(req: Request, { params }: { params: { id: string } }) {
  const session = await auth()
  if (!session) return Response.json({ error: 'Unauthorized' }, { status: 401 })
  
  const post = await db.query.posts.findFirst({
    where: eq(posts.id, params.id)
  })
  
  if (!post) return Response.json({ error: 'Not found' }, { status: 404 })
  if (post.userId !== session.user.id) {
    return Response.json({ error: 'Forbidden' }, { status: 403 })
  }
  
  await db.delete(posts).where(eq(posts.id, params.id))
  return Response.json({ success: true })
}
3

Validate all input data

Use schema validation on every endpoint that accepts data.

Code Example
import { z } from 'zod'

const CreatePostSchema = z.object({
  title: z.string().min(1).max(200).trim(),
  content: z.string().min(1).max(50000),
  tags: z.array(z.string().max(50)).max(10).optional()
})

export async function POST(req: Request) {
  const body = await req.json()
  const result = CreatePostSchema.safeParse(body)
  if (!result.success) {
    return Response.json({ error: 'Invalid input', details: result.error.flatten() }, { status: 400 })
  }
  // Use result.data (typed and validated)
}
4

Add rate limiting

Prevent abuse by limiting request frequency.

Code Example
import { Ratelimit } from '@upstash/ratelimit'
const ratelimit = new Ratelimit({ redis: Redis.fromEnv(), limiter: Ratelimit.slidingWindow(10, '10 s') })

export async function POST(req: Request) {
  const ip = req.headers.get('x-forwarded-for') ?? 'unknown'
  const { success } = await ratelimit.limit(ip)
  if (!success) return Response.json({ error: 'Too many requests' }, { status: 429 })
  // Process request
}
5

Return sanitized error messages

Never expose internal details, stack traces, or database errors to clients.

Code Example
export async function POST(req: Request) {
  try {
    // ... process request
  } catch (error) {
    console.error('API Error:', error) // Log full error server-side
    return Response.json(
      { error: 'An unexpected error occurred' }, // Generic message to client
      { status: 500 }
    )
  }
}
6

Scan your API endpoints

Use VAS to scan your application for unprotected endpoints, missing headers, and other API security issues.

What You'll Achieve

Every API endpoint has authentication, authorization, input validation, rate limiting, and sanitized error handling. Your API is protected against unauthorized access, data injection, and abuse.

Common Mistakes to Avoid

Mistake

Checking authentication but not authorization

Fix

Authentication verifies who the user is. Authorization verifies they can access the specific resource. Always check both.

Mistake

Returning detailed error messages in production

Fix

Log detailed errors server-side but return generic messages to clients. Error details help attackers understand your system.

Mistake

Only validating on the client side

Fix

Client-side validation is for UX. Server-side validation is for security. Always validate on the server.

Frequently Asked Questions

Should every endpoint require authentication?

Public endpoints (login, register, public content) do not require authentication. All other endpoints should require it. When in doubt, require authentication and make exceptions.

Is HTTPS enough to secure my API?

HTTPS encrypts data in transit but does not protect against application-level attacks like SQL injection, XSS, or unauthorized access. You need application-level security on every endpoint.

Ready to Secure Your App?

VAS automatically scans your deployed app for the security issues covered in this guide. Get actionable results in minutes.

Start Security Scan