Every Server Action must verify authentication as its first operation. Server Actions compile to public HTTP POST endpoints — anyone on the internet can call them directly with a simple fetch request, bypassing your UI entirely. Even if you have middleware or layout-level auth checks, the action itself must independently verify the user because external guards can be misconfigured, incomplete, or bypassed. Without per-action auth, an attacker can invoke privileged operations like deleting data, changing settings, or accessing resources they should never reach.
Server Actions are exposed as public HTTP POST endpoints. Anyone with the endpoint URL can call them directly, bypassing your UI, middleware, and layout-level auth checks. Without explicit authentication inside each action, any unauthenticated user can invoke privileged operations.
BeforeMerge scans your pull requests against this rule and 3+ others. Get actionable feedback before code ships.
Next.js Server Actions compile down to public HTTP POST endpoints. They are not protected by:
middleware.ts route matchingAnyone who discovers (or guesses) the action ID can call it directly with fetch() or curl. If the action does not verify authentication internally, it is an unauthenticated API endpoint.
Every Server Action that reads or writes data must verify authentication as its first operation, before any business logic runs.
"use server";
export async function updateProfile(data: ProfileData) {
// No auth check! Anyone can call this endpoint.
const supabase = await createClient();
await supabase
.from("profiles")
.update(data)
.eq("id", data.userId); // userId comes from the client — can be forged
}"use server";
import { createClient } from "@/lib/supabase/server";
export async function updateProfile(data: ProfileData) {
const supabase = await createClient();
const { data: { user }, error } = await supabase.auth.getUser();
if (error || !user) {
throw new Error("Unauthorized");
}
// Use the verified user.id, not the client-supplied one
await supabase
.from("profiles")
.update({ name: data.name, bio: data.bio })
.eq("id", user.id);
}getUser(), never from function argumentsSearch for Server Action files that don't call getUser():
# Find all "use server" files
grep -rl '"use server"' app/ --include="*.ts"
# Check which ones lack auth verification
for f in $(grep -rl '"use server"' app/ --include="*.ts"); do
if ! grep -q "getUser" "$f"; then
echo "MISSING AUTH: $f"
fi
donerequireAuth() helper that calls getUser() and throws on failurerequireAuth() as the first line of every Server Actionuser.id for all authorization and data queries