~3 min read • Updated Oct 26, 2025
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.
Written & researched by Dr. Shahin Siami