Server Auth Actions
Source: .agents/references/coding-standard/vercel-react-best-practices/rules/server-auth-actions.md
Metadata
- title: Authenticate API Routes Like Public Endpoints
- impact: CRITICAL
- impactDescription: prevents unauthorized access to server mutations
- tags: server, api-routes, pages-api, authentication, security, authorization
Content
This repo: Pages Router only — authenticate
src/pages/api/handlers. Server Actions ("use server") are not used; skip App Router Server Action examples unless migrating.
Authenticate API Routes Like Public Endpoints
Impact: CRITICAL (prevents unauthorized access to server mutations)
pages/api routes are public HTTP endpoints. Always verify authentication and authorization inside each handler — do not rely only on client-side checks or page-level guards, since the route can be called directly.
Incorrect (no authentication check):
import type { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { userId } = req.body
// Anyone can call this! No auth check
await db.user.delete({ where: { id: userId } })
return res.status(200).json({ success: true })
}
Correct (authentication inside the handler):
import type { NextApiRequest, NextApiResponse } from 'next'
import { verifySession } from '@/lib/auth'
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const session = await verifySession(req)
if (!session) {
return res.status(401).json({ error: 'Must be logged in' })
}
const { userId } = req.body
if (session.user.role !== 'admin' && session.user.id !== userId) {
return res.status(403).json({ error: 'Cannot delete other users' })
}
await db.user.delete({ where: { id: userId } })
return res.status(200).json({ success: true })
}
With input validation:
import type { NextApiRequest, NextApiResponse } from 'next'
import { verifySession } from '@/lib/auth'
import { z } from 'zod'
const updateProfileSchema = z.object({
userId: z.string().uuid(),
name: z.string().min(1).max(100),
email: z.string().email()
})
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const validated = updateProfileSchema.parse(req.body)
const session = await verifySession(req)
if (!session) {
return res.status(401).json({ error: 'Unauthorized' })
}
if (session.user.id !== validated.userId) {
return res.status(403).json({ error: 'Can only update own profile' })
}
await db.user.update({
where: { id: validated.userId },
data: { name: validated.name, email: validated.email }
})
return res.status(200).json({ success: true })
}
Reference: Next.js Pages API Routes