Treat every Server Action as a public mutation boundary
A Server Action may be imported by a component, but a client can still submit a request to invoke it. The UI is not an access-control layer. Design actions as you would a POST endpoint: assume an attacker can alter field values, replay a request, call the action without using your intended screen, and try identifiers that belong to another account.
Input is untrusted
FormData, hidden fields, route values, and client state are all attacker-controlled until the server parses and validates them.
Identity is server-owned
Read the session or token inside the action. Never accept a user ID, role, tenant, or permission decision from the browser.
Effects need controls
Database writes, emails, payments, and external calls need authorization, replay protection, logging, and a clear failure contract.
Keep the action small: parse input, load the trusted actor, apply a policy, perform one bounded mutation, and return a safe result. Put reusable domain rules in a server-only module so they can also serve API routes, background jobs, and administrative tools. That boundary makes reviews easier and prevents client bundles from accidentally importing privileged code.
Parse and validate before doing any work
Convert FormData to a narrow object, reject unknown or malformed values, and enforce limits before querying the database or calling a provider. Validation should describe business inputs, not merely TypeScript's expected shape. A string can be typed as an email but still be too long, malformed, or disallowed for the current workflow.
"use server";
import { z } from "zod";
import { requireUser } from "@/lib/auth";
import { updateProject } from "@/lib/projects";
const projectInput = z.object({
projectId: z.string().uuid(),
name: z.string().trim().min(1).max(120),
});
export async function renameProject(formData) {
const input = projectInput.safeParse(Object.fromEntries(formData));
if (!input.success) return { ok: false, error: "Check the project name." };
const actor = await requireUser();
await updateProject({ actorId: actor.id, ...input.data });
return { ok: true };
}Use allow-lists for enum values and file types, normalize values deliberately, and set maximum lengths for text, arrays, and uploads. Do not trust browser-side validation as a substitute; it improves usability but provides no protection. For wider endpoint conventions, our Next.js API route handler guide covers error contracts and validation at API boundaries.
Plan for replay, automation, and operational failures
A valid action can still be abused. Rate-limit sign-up, invitation, password-reset, export, and expensive AI actions by an appropriate combination of account, IP, tenant, and action type. Use a durable idempotency key for operations that create payments, orders, or external side effects, so retries return the original outcome instead of creating duplicates.
- Return generic, user-safe errors; log structured internal details with an opaque request or action ID.
- Do not expose stack traces, tokens, authorization decisions, or whether a private record exists.
- Use timeouts and retry policies around external providers; queue slow work instead of holding a user request open.
- Record actor, target type, target ID, action name, outcome, and deployment version for sensitive changes.
- Invalidate or revalidate only the paths and tags affected after a successful mutation.
Server Actions include framework protections, but application controls still matter. Avoid broad cross-origin assumptions, keep allowed origins explicit when deployment architecture requires them, and test a submitted action with modified values and a different account. Pair mutation logs with the request IDs and alerting patterns in our Next.js observability guide to make an incident traceable without collecting secrets.
Next.js Server Actions security checklist
✓ Treat every action as a public POST-style boundary
✓ Parse and validate all data on the server
✓ Load identity from a trusted server-side session
✓ Authorize the actor against the specific resource
✓ Scope data access by tenant or ownership
✓ Rate-limit expensive and high-risk mutations
✓ Use idempotency for externally visible side effects
✓ Return safe errors and record structured audit events
Ship safer Next.js mutations
Endurance Softwares helps teams build secure, maintainable Next.js applications with clear authorization boundaries, reliable APIs, and production-ready delivery practices.
