Lesson 15 / 25
Securing Server Actions
Every action is a public endpoint.
Authenticate, authorise, validate
A Server Action can be called with any arguments by anyone who can reach your site, just like an API endpoint. Inside every action: authenticate the user (read the session), authorise the specific operation (may this user edit this record?), and validate all input with a schema (for example Zod) rather than trusting form fields. Return safe error messages, avoid leaking internal details, and rate-limit sensitive actions.
A guarded action (sketch)
Pattern for real actions; getSession and db are your own modules. Not run here.
"use server";
import { z } from "zod";
import { getSession } from "@/lib/auth";
const Input = z.object({ id: z.string().uuid(), price: z.number().positive().max(1_000_000) });
export async function setPrice(raw: unknown) {
const session = await getSession();
if (!session) return { error: "Please sign in" }; // authenticate
const input = Input.safeParse(raw);
if (!input.success) return { error: "Invalid input" }; // validate
const product = await db.product.find(input.data.id);
if (product.ownerId !== session.userId) return { error: "Not allowed" }; // authorise
await db.product.update(input.data.id, { price: input.data.price });
return { ok: true };
}Never trust hidden fields
Users can change any value sent from the browser, including hidden inputs and ids; check permissions on the server.
Quick check: Why must Server Actions check authorisation?
- They can be called directly with any arguments, like public endpoints
- React checks permissions automatically
- They only run in development
- Forms cannot be submitted by attackers
Answer
They can be called directly with any arguments, like public endpoints — Treat actions as public APIs.