Next.js rejects a Server Action request whose Origin header does not match the app's host. What does that built-in check actually buy you, what does it not cover, and when do you have to configure allowed origins for it?
answer
- POST only, so no passive triggers
- a browser sets Origin, scripts cannot
- says nothing about who you are
- proxies rewrite the host
- list origins, don't disable
basics
~20 sNext.js only dispatches Server Actions over POST and compares the request's Origin against the host, rejecting mismatches — that stops another site invoking your action with the user's cookies. It does not authenticate, authorize, or validate anything, and proxies that rewrite the host need allowed origins configured.
solid answer
~50 sTwo things happen automatically. Server Actions are dispatched only over POST, so no `<img>` or link can trigger one; and Next compares the request's `Origin` header with the host it believes it is serving, rejecting the call when they disagree. That closes the specific hole of a third-party page firing your action while the browser attaches the user's session cookie — a browser cannot forge `Origin`. What it does not do is anything about identity or content: a request from a script that sets its own headers, or from a valid session that simply should not be allowed to do this, sails through, so authentication, authorization, and schema validation still belong in the action body. The check needs configuring when your deployment sits behind a proxy, load balancer, or CDN that rewrites the host, or when you serve the same app on several domains: you list the extra hosts under the `serverActions.allowedOrigins` option in `next.config`, which lives under `experimental` in current versions.
go deeper
Know that Server Actions are POST-only and that Next compares the request's Origin with the host, and that this is about which site started the request — not about who the user is.
Explain why Origin is trustworthy for browser-initiated traffic and why POST-only rules out passive triggers, and be able to name the framework check as one layer rather than the whole story.
Enumerate what the check does not cover — identity, permission, payload, non-browser clients, route handlers — and diagnose the proxy case where reads work and every mutation fails.
Own the deployment-topology consequence: decide how allowed origins are managed across preview, staging and multi-domain estates, and set the body-size and rate-limiting policy for endpoints reachable before authentication.
## What the framework does for you Two protections are built into the Server Actions dispatcher and cost you nothing. **Method restriction.** Actions are invoked with POST only. That matters because the passive triggers a page can force on a visitor's browser — an image source, a stylesheet link, a plain hyperlink, a prefetch — are all GETs. None of them can reach an action. A cross-site `<form>` can POST, which is why the second check exists. **Origin/host comparison.** For each action request Next compares the `Origin` header against the host it is serving under (including the forwarded-host header when it is present) and rejects the request on mismatch. `Origin` is one of the headers a browser sets itself and page JavaScript cannot override, so this reliably distinguishes "a request initiated by a document on my own site" from "a request initiated by a document on someone else's site." Together these mean a third-party page cannot make a visitor's browser invoke your action while the browser helpfully attaches the session cookie. ## What it emphatically does not do The senior half of this question is the list of things the check is silent about. - **It is not authentication.** A request with a valid `Origin` and no session at all passes the check. Whether there is a logged-in user is entirely your action's problem. - **It is not authorization.** A legitimately logged-in user, using your real UI on your real domain, produces a perfectly well-formed request for an action they have no business calling. Origin says nothing about permission. - **It is not validation.** The body is still arbitrary. Field types, lengths, and unexpected keys are unaffected. - **It does not constrain a non-browser client.** A script, a cURL invocation, or a Postman request sets any header it likes, including `Origin`. The check is a browser-behaviour check; it constrains what *other websites* can make *your users'* browsers do. It does not constrain an attacker acting as themselves, and if they have obtained a session token they can replay with it. - **It does not cover your other endpoints.** Route handlers in `route.ts` are ordinary HTTP endpoints with their own methods and their own exposure; this dispatcher-level behaviour is specific to action invocations. Because of that last group, "Next protects Server Actions out of the box" is a half-true statement that fails an interview when left unqualified. The correct framing: the framework removes one specific cross-site initiation vector, and every other control is still yours. ## When you must configure it The comparison is only as good as the host the server believes it has. Real deployments break that assumption: - a reverse proxy, load balancer, or CDN that terminates TLS and forwards under a different host name; - an ingress that rewrites the `Host` header; - one app served on several domains or on a preview domain plus a production domain; - a local tunnel used for webhook or mobile testing. In those cases legitimate requests get rejected, and the fix is to declare the acceptable hosts rather than to disable the check. Next exposes this as the `allowedOrigins` list inside the `serverActions` options in `next.config`, which sits under `experimental` in current versions: ```js // next.config.js module.exports = { experimental: { serverActions: { allowedOrigins: ['my-proxy.example.com', '*.staging.example.com'], }, }, } ``` The same options object carries `bodySizeLimit`, which caps how large an action request body may be — a small default that exists so an unbounded upload cannot arrive by accident. Raising it is a deliberate decision about how much work an unauthenticated POST is allowed to cost you. ## Diagnosing it in the wild The symptom of a misconfigured deployment is distinctive: the app renders fine, navigation works, reads work, and only mutations fail — the same action succeeds locally and fails behind the proxy. When you see "forms work on localhost, break in staging," check the forwarded host before you suspect your auth layer. Conversely, if you find yourself reaching for a setting that turns the comparison off entirely, you are removing the one protection the framework gave you in order to paper over a proxy misconfiguration; list the origin instead. ## How to answer this in one breath "Actions are POST-only and Next rejects requests whose Origin doesn't match the host, so another site can't invoke them with my user's cookies. That's the whole guarantee — it is not authn, authz, or validation, and it says nothing about a scripted client. If a proxy rewrites the host I add the real origins to the serverActions allowedOrigins list rather than disabling the check."
- A Server Action works locally but every submission fails after deploying behind a load balancer. What do you check first?The host the server sees versus the origin the browser sends. A proxy that terminates TLS and forwards under an internal host name makes the comparison fail, so reads succeed and every mutation is rejected. I confirm with the forwarded-host header in the request, then add the public origin to the `serverActions.allowedOrigins` list rather than turning the check off.
- Does this built-in check mean you can skip a CSRF token library entirely?For Server Actions specifically, the framework already performs the origin comparison, so a separate token is usually redundant. But it only covers action dispatch — any `route.ts` handler that mutates state on POST is on its own, and so is anything invoked from a non-browser client. I decide per endpoint, not per framework claim.
- Why does the body size limit belong in the same conversation?Because an action endpoint accepts POSTs before your code has decided anything about the caller. `bodySizeLimit` in the `serverActions` options bounds how much data an unauthenticated request can push through parsing, which is the cheap structural defence; anything beyond that — per-user rate limiting, upload quotas — has to be your own layer.
saying these in an interview costs you the question
- Origin checking means actions don't need auth checks
- Any client can spoof Origin, so the check is worthless
- The check also protects route handlers in route.ts
- Disable the check when a proxy breaks it
- It validates the request body as well as the sender