--- title: Authenticate Server Actions Like API Routes impact: CRITICAL impactDescription: prevents unauthorized access to server mutations tags: server, server-actions, authentication, security, authorization --- ## Authenticate Server Actions Like API Routes **Impact: CRITICAL (prevents unauthorized access to server mutations)** Server Actions (functions with `"use server"`) are exposed as public endpoints, just like API routes. Always verify authentication and authorization **inside** each Server Action—do not rely solely on middleware, layout guards, or page-level checks, as Server Actions can be invoked directly. Next.js documentation explicitly states: "Treat Server Actions with the same security considerations as public-facing API endpoints, and verify if the user is allowed to perform a mutation." **Incorrect (no authentication check):** ```typescript 'use server' export async function deleteUser(userId: string) { // Anyone can call this! No auth check await db.user.delete({ where: { id: userId } }) return { success: true } } ``` **Correct (authentication inside the action):** ```typescript 'use server' import { verifySession } from '@/lib/auth' import { unauthorized } from '@/lib/errors' export async function deleteUser(userId: string) { // Always check auth inside the action const session = await verifySession() if (!session) { throw unauthorized('Must be logged in') } // Check authorization too if (session.user.role !== 'admin' && session.user.id !== userId) { throw unauthorized('Cannot delete other users') } await db.user.delete({ where: { id: userId } }) return { success: true } } ``` **With input validation:** ```typescript 'use server' 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 async function updateProfile(data: unknown) { // Validate input first const validated = updateProfileSchema.parse(data) // Then authenticate const session = await verifySession() if (!session) { throw new Error('Unauthorized') } // Then authorize if (session.user.id !== validated.userId) { throw new Error('Can only update own profile') } // Finally perform the mutation await db.user.update({ where: { id: validated.userId }, data: { name: validated.name, email: validated.email } }) return { success: true } } ``` Reference: [https://nextjs.org/docs/app/guides/authentication](https://nextjs.org/docs/app/guides/authentication)