Skip to the solution
HardFrontend6 min read

What are the security considerations for Server Actions in Next.js?

764 views

The problem

Question presented to candidate: "Your team moved all mutations to Server Actions. What are the security risks, which ones does Next.js handle for you, and what must every action still do?"

What a strong answer should cover:

  • Every exported action is a public HTTP endpoint: its id is in client code, and anyone can call it with a POST.
  • So every action must authenticate and authorise the caller itself; hiding the button is not access control.
  • Arguments and FormData come from the network: validate types and ranges on the server.
  • Next.js adds protections: POST only, an Origin versus Host check against CSRF, encrypted non-deterministic ids, removal of unused actions from the client bundle, encrypted closure variables, a 1 MB default body limit.
  • Keep return values minimal, because they are sent to the browser, and keep secrets out of action closures anyway.

Clarifying questions expected:

  • "Where is authentication checked today: in the page or in the action?" β€” it must be in the action.
  • "Is the app behind a reverse proxy?" β€” the Origin check may need experimental.serverActions.allowedOrigins.

Code / implementation expected: Yes β€” an action with authentication, authorisation and validation.

  • nextjs
  • server-actions
  • security
  • validation

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

6 min read Β· 8 sections

Read the code

An action that checks identity, ownership and input
// app/posts/actions.ts
"use server";
import { z } from "zod";
import { revalidateTag } from "next/cache";
import { requireUser } from "@/lib/dal";    // throws if there is no valid session

const Input = z.object({ id: z.string().uuid() });

export async function deletePost(raw: unknown) {
  const user = await requireUser();                       // authentication
  const { id } = Input.parse(raw);                         // validation
  const post = await db.post.findUnique({ where: { id } });
  if (!post || post.authorId !== user.id) {               // authorisation
    return { ok: false as const, error: "Not allowed" };
  }
  await db.post.delete({ where: { id } });
  revalidateTag("posts", "max");
  return { ok: true as const };                             // minimal return value
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 111 of 129 in Next.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Next.js questions
Up nextHard

What is the RSC payload and how does Next.js stream it to the browser?

Continue