Server Actions Security

Why you can never trust the Client payload.

Next.js4 min readConcept 37 of 65

Public Endpoints

It is a common misconception that Server Actions are 'internal' functions safely hidden away on the server.

In reality, Next.js compiles every Server Action into a public HTTP POST endpoint. Anyone with the URL can trigger it from outside your application.

Zero Trust Architecture

Because they are public endpoints, you must treat them with the exact same paranoia as a traditional REST API route.

You cannot trust the data being sent from the client, and you must verify that the user making the request is authorized to perform the action.

Internal Validation

Always retrieve the user's session securely *inside* the body of the Server Action.

Never accept sensitive identifiers (like userId or role) as arguments passed from the client, because a malicious user can simply change the arguments in their network request.

The Payload Forgery

Hover over the 'Bad Practice' and 'Good Practice' tabs to see how an attacker can manipulate arguments passed from the client, and how to prevent it by reading the session securely on the server.

Malicious POST Request

Network Tab (Attacker) Payload Forged
POST /_next/data/...
Content-Type: application/json

// Payload modified by attacker in browser:
{
"actionId": "deleteAccount",
"args": [
"user_id_of_victim_889"
]
}

Server Action

actions.ts
'use server';

export async function deleteAccount(userId) {
// VULNERABLE: Blindly trusting client input!
// Attacker can delete ANY account by changing the payload.
await db.accounts.delete(userId);
}

Check yourself

Pick an answer to lock it in, then read why. Getting one wrong is part of how it sticks.

  1. 1What are Server Actions compiled into under the hood?
  2. 2Why is it dangerous to pass `userId` as an argument from the Client to a Server Action?

Remember this

  • Server Actions are exposed as public HTTP POST endpoints.
  • Treat them with the same security paranoia as traditional API routes.
  • Never trust sensitive identifiers (userId, role) passed in as arguments.
  • Always validate the user's session and authorization INSIDE the action body.
  • Use libraries like Zod to validate the incoming data payload.

Done with this concept?

Mark it complete to track your progress. No login needed.