Web TippsUse custom web fonts in Google Sheets charts(08.09.2026 um 17:05 Uhr)
Web TippsIntroducing the new 1Password App for Google Chat(08.09.2026 um 18:02 Uhr)
Web TippsUse custom web fonts in Google Sheets charts(08.09.2026 um 17:05 Uhr)
Web TippsIntroducing the new 1Password App for Google Chat(08.09.2026 um 18:02 Uhr)

🔧 Programmierung 🕛 vor 1 Jahr 11 Min Lesezeit SECURITY-FEED
0

Next.js server action security

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

were as a new way to process data on the server in response to a client interaction. A server action is like an asynchronous API route, but more tightly coupled to the UI so the data mutation code is closer to where it is triggered.



Server actions are an elegant way to handle user actions, such as button clicks or form submissions, because much of the boilerplate is handled for you - they are invoked through automatically generated POST requests. Next.js hides most of the request details so your code can focus just on processing the data.



unique, non-deterministic ID references for server actions to make it more difficult to locate and reference the APIs. However, this is just security by obscurity and the endpoint can still be found within the client code or when triggering a request to raw server action.



Unused server actions will not have their IDs exposed to the client-side JavaScript bundle. However, if a user were to get the handle to the ID of an in-use action, they could still be invoked with any arguments.



The action ID can be derived from the Next-Action request header.



.



This also has implications for self-hosting Next.js because the generated encryption keys will be different on each server. You will need to handle syncing the encryption keys to ensure requests that round-robin to different servers work correctly.



POST APIs have built-in protection from in your Next.js config.



These are the architectural security implications. Once you've considered these, you then need to implement usual API security protections, which we'll explore below.





Securing a from submission server action



Once you have considered the security implications described above, you can then proceed to add additional protection by setting up input validation and installing Arcjet.



, and block traffic from automated clients and bots. We will also validate data using the , which creates request objects that allow access to the headers for analyzing the request.




CODE
'use server'
import arcjet, { shield, detectBot, fixedWindow, request } from '@arcjet/next';
import { registrationSchema, type RegistrationData } from './lib/schema'






Define what the response will be - either an error message or a success message.




CODE
type RegisterResponse = {
error?: string;
success?: string;
};






Now, configure the rules for the Arcjet protection measures. prevents all automated clients from submitting the form, and

Registration form showing the rate limit error.



Next, create an object named data that will collect the form fields from the submission using the .get() method on the formData object that is sent to this function from the form.




CODE
  const data = {
email: formData.get('email') || '',
password: formData.get('password') || '',
confirmPassword: formData.get('confirmPassword') || ''
};






Parse the data object against the validation schema using the .safeParse() Zod method and store this evaluation in the result variable. If the validation check fails, the fields responsible will display their error messages. If the check passes, the registration to a user database is simulated with a message printed to the terminal and a success message is displayed.



Finally, to handle any unexpected errors, we include the catch block at the end.




CODE
  try {
const result = registrationSchema.safeParse(data);
if (!result.success) {
return {
error: result.error.errors[0].message
};
}

const validatedData: RegistrationData = result.data;

// This is where you would normally save the user to a database.
console.log("Database would register:", { email: validatedData.email });

return {
success: "Registration successful!"
};

} catch (error) {
console.error('Registration error:', error);
return {
error: "An error occurred during registration."
};
}
}









/src/app/components/form.tsx



Now, let's create the form component of the webpage. Begin with the 'use client' annotation to specify that this component runs in the browser.



Next, import the necessary hooks, the server action function, and validation schema.




CODE
'use client'

// Next.js hook that manages form state and server action responses.
import { useActionState } from 'react'

// React's built-in hook for managing local component state.
import { useState } from 'react'

// Import our server action function that handles form submission.
import { registerUser } from '../actions'

// Import our Zod schema that defines validation rules.
import { registrationSchema } from '../lib/schema'






Define the initial state of the form using undefined values since there are no error or success messages before the first form submission.




CODE
const initialState = {
error: '',
success: undefined
} as const






At the beginning of the form creation function:



The useActionState hook takes two arguments: the server action function registerUser and the initialState that we just defined. In the tuple, state stores the current error or success messages and formAction is the client-side function that triggers the registerUser server action.



The useState hook creates an object of key-value pairs consisting of the form field names and their respective error messages. In the tuple, validationErrors stores these messages and setValidationErrors is responsible for updating them.




CODE
export default function RegisterForm() {
const [state, formAction] = useActionState(registerUser, initialState)
const [validationErrors, setValidationErrors] = useState<Record<string, string>>({})






The handleSubmit client-side function runs whenever the form is submitted. Upon a form submission, setValidationErrors({}) clears any previous messages. The form data is collected and stored in the data object variable and compared against the validation schema.




CODE
  const handleSubmit = async (formData: FormData) => {
setValidationErrors({})

const data = {
email: formData.get('email')?.toString() || '',
password: formData.get('password')?.toString() || '',
confirmPassword: formData.get('confirmPassword')?.toString() || '',
}

const result = registrationSchema.safeParse(data)






If validation fails, it displays the errors locally without making a server call. If validation passes, formAction(formData) passes the form data to the server and calls the registerUser server action function.




CODE
    if (!result.success) {
const errors: Record<string, string> = {}
result.error.errors.forEach((error) => {
const field = error.path[0].toString()
errors[field] = error.message
})
setValidationErrors(errors)
return // Stop here - don't submit invalid data.
}

await formAction(formData)
}






The form that will be rendered will call handleSubmit which subsequently calls the server action if validation passes. If the check does not pass, the appropriate error messages will be sourced from validationErrors and displayed to the user.




CODE
return (
<form action={handleSubmit}>
<div>
<label htmlFor="email">Email:</label>
<input
type="email"
id="email"
name="email"
required
/>
{validationErrors.email && (
<p>{validationErrors.email}</p>
)}
</div>

<div>
<label htmlFor="password">Password:</label>
<input
type="password"
id="password"
name="password"
required
/>
{validationErrors.password && (
<p>{validationErrors.password}</p>
)}
</div>

<div>
<label htmlFor="confirmPassword">Confirm Password:</label>
<input
type="password"
id="confirmPassword"
name="confirmPassword"
required
/>
{validationErrors.confirmPassword && (
<p>{validationErrors.confirmPassword}</p>
)}
</div>

{state?.error && (
<p>{state.error}</p>
)}

{state?.success && (
<p>{state.success}</p>
)}

<button type="submit">Register</button>
</form>
)
}









/src/app/page.tsx



Finally, import the form on the app's landing page.




CODE
import RegisterForm from './components/form'

export default function Home() {
return (
<main>
<h1>Register Account</h1>
<RegisterForm />
</main>
)
}






Test the Protections



To test your web application run npm run dev and visit:

Console output showing the form submissions.



Using an HTTP proxy tool, we can test the validation performed server-side:



for more tips. Next.js also has server actions security documentation worth reviewing.

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:
Community Threat-Level Barometer
Live Votum

Wie stufst du das Risiko dieser Schwachstelle / Bedrohung für dein Unternehmen ein?

Noch keine Stimmen — schätze das Risiko als Erster ein.

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
3 Quellen
Use custom web fonts in Google Sheets charts
2 Quellen
Introducing the new 1Password App for Google Chat
1 Quelle
Context-aware access controls are available for Gemini Enterprise in the Admin console
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Next.js server action security

Thematisch verwandte Begriffe: Nextjs, server, action, security · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...