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
Add authentication to every protected endpoint
Verify the user is authenticated before processing any request that accesses or modifies data.
// 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' })
}
}Add authorization checks
After authentication, verify the user has permission to access the specific resource.
// 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 })
}Validate all input data
Use schema validation on every endpoint that accepts data.
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)
}Add rate limiting
Prevent abuse by limiting request frequency.
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
}Return sanitized error messages
Never expose internal details, stack traces, or database errors to clients.
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 }
)
}
}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