Mutating Data Securely in Next.js – Server Actions, Input Validation, and CSRF Protection

Next.js uses Server Actions to handle data mutations like form submissions, database updates, and user logout. This article explains how Server Actions work, their built-in security features, how to validate client input, manage encryption keys, prevent CSRF attacks, and avoid side effects during rendering. It also includes auditing tips for secure Next.js applications.

Server ActionsInput validationEncryptionCSRF protection

~3 دقیقه مطالعه · آخرین به‌روزرسانی ۴ آبان ۱۴۰۴

Introduction


In Next.js, data mutations—such as adding items, logging out, or updating a database—are handled using Server Actions. These actions are treated as public HTTP endpoints and must be secured accordingly with proper validation and authorization.


Built-in Server Actions Security Features


  • Secure IDs: Next.js generates encrypted, non-deterministic IDs for each Server Action
  • Dead Code Elimination: Unused actions are removed during build and not exposed

IDs are regenerated every 14 days or when the build cache is invalidated. Despite these protections, Server Actions should be treated like public endpoints and secured accordingly.


Validating Client Input


Client inputs such as form data, URL parameters, headers, and searchParams must be validated. Example:

// BAD: trusting searchParams directly
const isAdmin = searchParams.get('isAdmin')
if (isAdmin === 'true') return <AdminPanel />

// GOOD: verify using server-side token
const token = cookies().get('AUTH_TOKEN')
const isAdmin = await verifyAdmin(token)
if (isAdmin) return <AdminPanel />

Authentication and Authorization


Every Server Action should verify that the user is authorized to perform the mutation:

'use server'

import { auth } from './lib'

export function addItem() {
  const { user } = auth()
  if (!user) throw new Error('You must be signed in to perform this action')
  // ...
}

Closures and Encryption


Defining a Server Action inside a component creates a closure that captures outer variables. Example:

export default async function Page() {
  const publishVersion = await getLatestVersion()

  async function publish() {
    "use server"
    if (publishVersion !== await getLatestVersion()) {
      throw new Error('Version has changed')
    }
  }

  return (
    <form>
      <button formAction={publish}>Publish</button>
    </form>
  )
}

Next.js encrypts closed-over variables and generates a new private key for each build. However, encryption alone should not be relied on to protect sensitive data.


Managing Encryption Keys Across Servers


For self-hosted deployments, use NEXT_SERVER_ACTIONS_ENCRYPTION_KEY to ensure consistent encryption across servers. The key must be AES-GCM encrypted.


Preventing CSRF Attacks


Server Actions use POST requests and compare the Origin header with Host or X-Forwarded-Host. If they don’t match, the request is aborted. You can configure allowed origins:

module.exports = {
  experimental: {
    serverActions: {
      allowedOrigins: ['my-proxy.com', '*.my-proxy.com'],
    },
  },
}

Avoiding Side Effects During Rendering


Mutations should not occur during rendering. Example of incorrect usage:

// BAD
if (searchParams.get('logout')) {
  cookies().delete('AUTH_TOKEN')
}

Correct approach using Server Actions:

// GOOD
import { logout } from './actions'

<form action={logout}>
  <button type="submit">Logout</button>
</form>

Auditing a Next.js Project


  • Is there a dedicated Data Access Layer?
  • Do "use client" components receive private data?
  • Are Server Action arguments validated and re-authorized?
  • Are dynamic route params (e.g. /[param]/) validated?
  • Are proxy.ts and route.ts files audited for security?

Conclusion


Server Actions in Next.js offer a powerful way to handle mutations, but they must be secured with proper validation, authorization, and encryption. Avoid side effects during rendering, and audit your application regularly to ensure data integrity and protection.


نوشته و پژوهش‌شده توسط دکتر شاهین صیامی

مقالات مرتبط

Advanced Client-Side Routing and Performance Hooks in Next.js

Next.js provides a rich set of client-side hooks and caching utilities that empower developers to build dynamic, responsive, and secure applications. From reading route parameters to tracking navigation state and reporting performance metrics, this guide walks you through the most important tools available in the App Router.

ادامه

Handling Authorization and Caching in Next.js: A Developer’s Guide

Next.js introduces powerful experimental features for access control and smart caching. This guide covers the unauthorized() function for custom 401 handling, unstable_cache for persistent memoization, updateTag for instant cache invalidation, and useLinkStatus for inline navigation feedback. Learn how to use these tools to build secure, performant, and responsive applications.

ادامه

redirect and refresh in Next.js — Smart Redirects and Client Refreshing via Server Actions

The redirect function in Next.js allows you to navigate users to a new route, returning either a 307 or 303 HTTP response depending on context. It works in Server Components, Client Components, Route Handlers, and Server Actions. The refresh function is used exclusively within Server Actions to refresh the client router. This article explains how both functions work, with practical examples and key considerations.

ادامه

NextRequest and NextResponse in Next.js — Managing Cookies, Headers, Redirects, and Rewrites

Next.js extends the native Web Request and Response APIs with NextRequest and NextResponse, offering powerful tools for managing cookies, headers, redirects, rewrites, and JSON responses. These utilities simplify server-side logic and improve control over routing, personalization, and security. This guide walks through their capabilities with practical examples and best practices.

ادامه

headers, ImageResponse, notFound, and permanentRedirect in Next.js — Request Handling, Dynamic Images, Errors, and Redirects

Next.js offers powerful tools for handling HTTP requests and responses in Server Components. The headers function lets you read incoming request headers. ImageResponse allows you to generate dynamic images using JSX and CSS. The notFound function renders a custom 404 page, and permanentRedirect enables permanent redirection to another route. This article explains how to use each feature with practical examples.

ادامه

A Complete Guide to Using metadata and generateMetadata in Next.js

In modern versions of Next.js, managing page metadata is more powerful and intuitive than ever. Metadata is automatically injected into the <head> of your pages and plays a vital role in SEO, social sharing, and user experience. This guide explains the two main ways to define metadata: using the static metadata object and the dynamic generateMetadata function.

ادامه