Security11 min read

How to Secure Next.js Server Actions: Auth, Validation, and Rate Limiting

Next.js Server Actions are public POST endpoints anyone can call directly. This checklist covers authentication, authorization, input validation, and rate limiting to lock them down.

Next.js Server Actions security is widely misunderstood — and that misunderstanding ships vulnerabilities into production every day. When developers write a Server Action, it looks like a function: async function createInvoice(formData: FormData). It behaves like a function: you call it from a React component, the form submits, the data writes to the database. What it actually is, under the hood, is a publicly accessible HTTP POST endpoint. Any user, any script, any attacker with a network connection can invoke it directly with arbitrary arguments, bypassing every React component, every client-side validation, and every UI-level access check you wrote.

This became a broader concern as Next.js 13+ adoption has accelerated — especially in AI-generated codebases built with Cursor, Lovable, v0, and Windsurf. These tools generate Server Actions that work correctly in the happy path and fail silently under adversarial conditions. The generated code passes every functional test because the test runner calls the action the way the UI would. An attacker calls it differently.

This guide covers the full Server Actions security checklist: authentication, authorization, input validation, rate limiting, CSRF behavior, and the October 2026 CVEs every Next.js deployment must patch. For the broader category of authentication mistakes in AI-built apps, see the guide on common authentication mistakes in vibe-coded apps. For a full pre-launch security review of your SaaS product, the SaaS pre-launch security checklist covers every layer, not just the application tier.

Why Next.js Server Actions Are a Security Risk

Server Actions were introduced in Next.js 13 and became stable in Next.js 14. They let you write server-side logic that React calls directly from client components via form actions or programmatic invocation. The developer experience is excellent: no API routes, no fetch calls, no manual serialization. The security model is unforgiving: every Server Action is compiled into a unique POST endpoint at /_next/action/<hash>. That endpoint is publicly reachable on any deployed Next.js application.

  • →Direct invocation: an attacker can call your Server Action directly with fetch(), curl, or Burp Suite by replicating the POST request the browser makes. Client-side guards — disabled buttons, validation schemas, role checks in middleware — are irrelevant at this layer.
  • →No authentication by default: Next.js does not inject any auth context into Server Actions automatically. If you do not explicitly verify the session inside the action body, the action runs unauthenticated.
  • →Arbitrary input: React serializes form data or the arguments you pass from the client. An attacker POST-ing directly to the action endpoint can send any data shape, including fields your form does not expose.
  • →Stacked mutations: a single crafted request can invoke a Server Action with unexpected argument combinations, potentially triggering unintended state changes that the UI flow was designed to prevent.
  • →Chained IDOR: Server Actions that accept record IDs without ownership checks expose the same insecure direct object reference vulnerability that affects REST APIs — and it is often harder to spot because the action looks like a local function call in the code.

Authentication in Server Actions: Verifying the Session on Every Call

The most common Server Actions security mistake is relying on middleware for authentication. Next.js middleware runs at the edge before a request reaches your application, and it is a useful layer for redirecting unauthenticated users away from protected pages. It is not a security boundary for Server Actions. A request that bypasses the UI and POSTs directly to the action endpoint may not trigger your middleware at all, depending on your matcher configuration. Even if it does, middleware returning a redirect response does not prevent the Server Action from executing in some edge cases — this was part of the CVE-2025-29927 bypass class that affected Next.js 14 and early 15 deployments.

Authentication must happen inside the Server Action, every time, without exception. The pattern is to call your auth library's session-fetching function at the top of every action body before any other logic runs. If the session is null or invalid, throw an error or return early. This is true even if the action is only ever called from a component that is behind an authenticated route — because that is a UI constraint, not a server-side constraint.

  • →Call auth() or getSession() as the first line of every Server Action — not in a wrapper, not in middleware, not in a parent component.
  • →If using NextAuth v5 or Auth.js, the auth() function is the right call. It reads the session from the request cookie synchronously in the Server Action context.
  • →If using Supabase Auth, call createServerClient() with the Next.js cookies() adapter and then call supabase.auth.getUser() — not getSession(), which trusts the JWT without re-verifying it against the Supabase auth server.
  • →Never trust req.user, session.user, or any user data passed from the client. Always re-fetch the session from the server-side auth source inside the action body.
  • →Treat a missing or expired session as an unauthenticated request and throw an error immediately — do not proceed to the database layer.

Authorization: Ownership Checks Are Not Optional

Authentication tells you who is making the request. Authorization tells you whether they are allowed to perform this specific operation on this specific resource. Many Server Actions get authentication right and skip authorization entirely. The result is that any authenticated user can modify any other authenticated user's data by passing a different record ID. This is a classic Insecure Direct Object Reference (IDOR) vulnerability, and it is pervasive in AI-generated Next.js codebases.

An IDOR in a Server Action follows a predictable pattern: the action accepts an id parameter, fetches or updates a database record using that ID, and never verifies that the authenticated user owns or has permission to access that record. The fix is equally predictable: every database operation in a Server Action must include the authenticated user's ID as a constraint in the query. For a deeper look at how IDOR vulnerabilities manifest in multi-tenant SaaS applications built on Next.js, see the guide on IDOR vulnerabilities in multi-tenant SaaS.

  • →Every query that fetches a record by ID should include a WHERE userId = session.user.id clause (or the equivalent in your ORM). Never fetch a record by ID alone and then check ownership after — the fetch itself is the information disclosure.
  • →Every update or delete should constrain on both the record ID and the user ID: a single UPDATE with WHERE id = $1 AND userId = $2 is safer than a fetch-then-write pattern because it makes ownership verification atomic.
  • →Role-based operations should read the user's role from the database, not from client-supplied data. Never accept a role claim from the request body.
  • →In multi-tenant apps, check both the user's tenant membership and the resource's tenant assignment — a user who is authenticated and authorized within their own tenant must not reach resources belonging to another tenant.

Input Validation: Zod Schemas on the Server Side

Client-side validation is a user experience feature, not a security control. Any input that reaches a Server Action from the client must be validated server-side before it touches your database or business logic. The standard approach is to define a Zod schema that describes the shape of valid input and validate the parsed FormData or argument object against it at the top of the action body — after authentication, before any database call.

  • →Define Zod schemas for every Server Action that accepts external input. Include type constraints (z.string().uuid(), z.number().positive()), length bounds (z.string().max(1000)), and enum constraints for fields with a fixed set of valid values.
  • →Parse with safeParse() rather than parse() so you can return a structured error response instead of throwing an unhandled exception that leaks stack traces to the caller.
  • →Never pass FormData directly to a database query or ORM method. Always extract and validate each field individually using your schema.
  • →Validate array lengths and nested object depths to prevent stack overflow attacks through deeply nested input structures sent by an attacker.
  • →Reject unexpected fields explicitly — if your schema expects { title, body }, an action that processes an additional field like { title, body, adminOverride: true } may expose an unintended privilege escalation path.

Rate Limiting Next.js Server Actions in Production

Server Actions do not have rate limiting by default. An unauthenticated attacker — or an authenticated one — can call your action endpoint at whatever rate their connection allows. This matters for account creation actions (account enumeration), login-adjacent actions (credential stuffing), payment or billing actions (charge manipulation), and any action that sends email or SMS (cost amplification). Rate limiting belongs in the action body, applied before the main business logic runs.

  • →Use a Redis-backed rate limiter (Upstash Ratelimit, ioredis with a sliding window, or Vercel KV with a custom counter) keyed on the user ID for authenticated actions and on the IP address for unauthenticated ones.
  • →For unauthenticated Server Actions (account creation, password reset initiation), apply IP-based rate limiting and fingerprint-based rate limiting in combination — neither alone is sufficient against distributed attackers.
  • →Set limits that reflect realistic usage: 50 form submissions per minute from a single user is not human behavior; a password reset action called more than 5 times per hour from the same IP is suspicious.
  • →Return a structured error from the Server Action when a rate limit is hit and surface a user-friendly message. Do not silently swallow rate limit events — log them for abuse detection and alerting.
  • →Consider time-based lockout for high-value actions: after N failed attempts in a window, lock the action for that identity for M minutes and require additional verification before allowing further attempts.

CSRF Protection and the Next-Action Header

Next.js 14 introduced automatic CSRF protection for Server Actions via the Next-Action request header. When a Server Action is invoked through React's form action or useActionState APIs, Next.js adds this header automatically. Direct fetch() calls from same-origin code also include it. A cross-origin request that attempts to invoke a Server Action will not include the Next-Action header by default, which causes Next.js to reject it with a 403. This is effective protection against classic CSRF attacks from malicious third-party sites.

The protection has limits worth understanding. It does not help if your Content-Security-Policy or CORS configuration allows untrusted origins to make credentialed requests. It does not apply to actions you expose as conventional API routes. It does not replace SameSite cookie attributes — your session cookies should be SameSite=Strict or SameSite=Lax, never SameSite=None unless a specific cross-site use case requires it. Verify your CSP and security headers are set correctly; a DeployReady scan catches missing or misconfigured headers automatically before you deploy.

The October 2026 Next.js CVEs: Patch Before You Read the Rest

Three critical vulnerabilities were disclosed in October 2026 affecting Next.js versions before 16.4.2. If your deployment has not patched to Next.js 16.4.2 or later, stopping to upgrade is higher priority than the application-level checklist below.

  • →SVG Image Optimization RCE (CVE-2026-96889): a memory corruption bug in librsvg, which the sharp package uses to decode SVG images during Next.js image optimization, enables remote code execution on glibc-based Linux hosts. Affected versions: Next.js 16.0.0 through 16.4.1 on Linux with the sharp dependency installed. Fix: upgrade to Next.js 16.4.2 and sharp 0.35.5 or later.
  • →use cache Remote Code Execution: a deserialization vulnerability in the experimental use cache directive allows specially crafted cache payloads to execute arbitrary server-side code without authentication. Fix: upgrade to Next.js 16.4.2. If an immediate upgrade is not possible, disable the use cache directive entirely in next.config.
  • →Server Actions Denial of Service: a malformed request body sent to a Server Action endpoint can exhaust server memory and crash the Next.js process with no authentication required. Fix: upgrade to Next.js 16.4.2. As a temporary mitigation, place a reverse proxy with a request body size limit in front of your Next.js server.
  • →Earlier: CVE-2025-29927 (Middleware Authorization Bypass) — if you are still on Next.js 14 or early Next.js 15, patch to at least 15.2.4 or 14.2.25 for the middleware bypass fix. This vulnerability allowed requests with a specially crafted x-middleware-subrequest header to bypass middleware checks entirely, defeating auth guards written at the middleware layer.

Running DeployReady to Catch Server Action Security Gaps

DeployReady is Belsoft's open-source CLI that runs 30+ static and dynamic security checks against a Next.js application and returns a 0–100 readiness score with specific fix guidance. It catches many Server Actions security gaps automatically: missing Content-Security-Policy headers, exposed secrets in NEXT_PUBLIC_ environment variables, missing HSTS, Supabase RLS disabled on tables, and outdated dependency versions flagged against CVE databases. Run it in under a minute with npx deployready at your project root. For CI integration, add it as a gate that blocks deployments when the readiness score falls below your threshold.

DeployReady does not replace a manual security review. It catches the checks that are fast to automate: header configuration, secret exposure, dependency versions, and known misconfiguration patterns. What it cannot catch is application logic — whether your Server Actions verify ownership before updating records, whether authentication happens before authorization in the action body, whether your Zod schema is comprehensive enough to block injection. These require an engineer to read the code with an adversarial mindset.

Frequently Asked Questions

Does middleware protect Next.js Server Actions?

No. Next.js middleware is a routing and UX layer, not a security boundary for Server Actions. A direct HTTP POST to the action endpoint may bypass middleware entirely depending on your matcher configuration, and CVE-2025-29927 showed that specially crafted requests can bypass middleware even when it is configured correctly. Authentication and authorization checks must live inside the Server Action body itself.

Can Server Actions be called without authentication?

Yes, unless you explicitly check for a valid session inside the action. Next.js does not add any authentication layer to Server Actions automatically. Any Server Action that does not call your auth library and verify the session before proceeding is effectively a public endpoint, regardless of whether the UI component that calls it is behind a login wall.

How do you rate limit a Next.js Server Action?

Add a Redis-backed rate limiter at the top of the action body, before any business logic runs. Upstash Ratelimit is the most common choice for Vercel-hosted Next.js apps because it works at the edge without a persistent connection. Key the limiter on session.user.id for authenticated actions and on request IP for unauthenticated ones. Set limits appropriate to the action's legitimate use — a contact form submission does not need more than 5 per hour per user; a search action might allow 60 per minute.

What are the Next.js CVEs disclosed in October 2026?

Three CVEs were disclosed in October 2026 affecting Next.js before version 16.4.2: a Server Actions denial-of-service via malformed request bodies, an RCE via the use cache directive deserialization, and an SVG image optimization RCE (CVE-2026-96889) through the sharp/librsvg dependency on glibc-based Linux. All three are fixed in Next.js 16.4.2. Upgrade immediately if you are on any earlier version in the 16.x line.

Are Next.js Server Actions vulnerable to CSRF attacks?

Next.js 14+ includes automatic CSRF protection for Server Actions via the Next-Action request header, which cross-origin requests cannot replicate without authentication. This is effective against classic CSRF from third-party sites. It does not protect against same-origin request forgery, it does not substitute for correct SameSite cookie attributes, and it does not protect actions exposed as conventional API routes rather than Server Actions. Always set SameSite=Lax or SameSite=Strict on session cookies.

How Belsoft Helps Secure Next.js Applications

A Belsoft security audit covers every layer of a Next.js application: Server Actions authentication and authorization, input validation completeness, ownership check patterns, rate limiting configuration, Supabase or Prisma query safety, security header configuration, and dependency CVEs. We review application logic by hand — the checks that automated scanners cannot perform — and we run DeployReady as the automated baseline so your team can replicate the scan on every subsequent deploy. The audit ends with a prioritized fix list and, for Belsoft partnership clients, we implement the fixes alongside your team and retest before sign-off.

This matters most for SaaS teams approaching their first enterprise customer or security questionnaire, and for founders who shipped their product using Cursor, Lovable, v0, or another AI coding tool and have not yet had the codebase reviewed by an engineer with an adversarial mindset. If that describes your situation, book a security review call and we will scope the engagement in 30 minutes. The Belsoft security audit service covers the full stack — authentication, data access, API authorization, AI features — not just the checklist items in this guide.

“A Server Action that looks like a function call is a public API endpoint. Secure it like one.”

Written by

Marco Silva

Security Engineer, Belsoft Solutions

Ready to partner?

Let's talk about your company.

30 minutes. No pitch. We talk through how you run today and where AI and automation would help.

Book a free audit
logo

Your AI & automation transformation partner: we audit, build, and train your team, then stay embedded as you grow.

Member ofNVIDIA Inception Program

Copyright Ⓒ 2026 BelSoft. All Rights Reserved.

BELSOFT, LDA · NIPC 517893258 · Lisbon, Portugal