Server Actions with no auth check
A Server Action is a public POST. 'use server' does not mean only your button can call it.
Last updated
Shows up in: Next.js, Supabase, Vercel
What it is
Next.js Server Actions let a component call a server function without you writing a route. The framework still exposes that function as an HTTP endpoint. Anyone who can talk to your site can invoke it. The button is one client. It is not the only client.
"use server" marks the function as server code. It does not check who the caller is. An action that updates a row using an id from its arguments, and never looks at the session, is an open write. Combined with the secret key, it also bypasses Supabase RLS.
Closures do not save you. The arguments the client passes are untrusted. A hidden input with userId is untrusted. Read the user from the session inside the action.
How to check in 5 minutes
Search for
"use server". Open every exported async function.For each, find the session lookup. If the first
awaitis a database call, the action is open.Check arguments. If the function trusts
userId,role,price, orisAdminfrom the caller, that is the bug even when a session exists.Sign out and trigger the action's endpoint if you know it, or temporarily call the action from a logged-out page on a staging deploy you own. A mutation that succeeds logged out is confirmed.
How to fix
Next.js
"use server";
export async function renameProject(projectId: string, name: string) {
const user = await getCurrentUser();
if (!user) throw new Error("Unauthorized");
const project = await db.project.findFirst({
where: { id: projectId, userId: user.id },
});
if (!project) throw new Error("Not found");
await db.project.update({ where: { id: project.id }, data: { name } });
}
Validate name for length and type. Do not spread the form object into data. That is how role sneaks in. See the client sets the price or role.
Middleware or proxy can be an extra filter. Keep the check in the action anyway.
Supabase
Inside the action, create the client from the user's cookies so RLS runs, or use the secret key only after the session check and add user_id to the query yourself. Do not create a module-level secret client and call it from every action by habit.
Vercel
Server Actions work on Vercel the same way. Deployment protection on a preview does not authorize production actions.
Firebase
If the action uses the Admin SDK, rules will not stop it. The session check in the action is the control.
Copy-paste prompt
Paste this into Cursor, Claude Code, or whichever agent is editing the repo. Read the diff before you merge it.
You are authorizing Server Actions. Do not add features. Find every "use server" function that reads or writes data. 1. Load the current user inside the function. If there is no user, throw or return an error before any write. 2. Ignore userId, role, price, and isAdmin arguments. Take identity from the session. 3. Scope updates and deletes to rows that user owns. 4. Do not pass the raw form object into a database update. Assign the fields you intend. 5. Leave the UI button as it is. The button is not the check. List each action and the check you added.
Questions
Are Server Actions CSRF-safe because Next.js checks the origin?
Next.js has protections so a random site cannot trivially submit an action with the user's cookies. That is not authorization. A request that passes the framework's origin check can still be from a logged-out caller or from the wrong user. Check the session and the owner.
Can I rely on the layout having redirected?
No. The action does not re-run your layout's redirect as its authorization. Check inside the function.