Closed lewisnewson closed 9 months ago
@lewisnewson #58019 (comment)
Fantastic, adding their recommendation of the following sorted the issue for me;
next.config.js
experimental.serverActions.allowedOrigins: ["https://nextjs-14-0-2-bug-report-4ea064a57303.herokuapp.com"]
So for those needing a hand with this, the above should work as fix.
Thanks for digging that out @JavierMartinz
can't get it working from VSCode desktop connected to a CodeSpace, tried with the following next.config.js
{
experimental: {
serverActions: {
allowedForwardedHosts: ['localhost'],
allowedOrigins: ['http://localhost']
},
}
}
Tried to add it to the next config but didn't work. Not sure where there is a breaking change in a minor version update anyways.
I found this thread following the same issue, and have tried canary
and the current stable with the same results.
@lewisnewson I noticed your commend mentions that you had this working in 14.0.1
, I am seeing different results having downgraded back to 14.0.1
, there are no error messages but the server action doesn't seem to make fetch
call to my REST service (validated by monitoring the logs of the REST API server).
I had a look at type definition for ExperimentalConfig
in 14.0.3-canary.5
and it doesn't have allowedForwardedHosts
as a property.
serverActions?: {
/**
* Allows adjusting body parser size limit for server actions.
*/
bodySizeLimit?: SizeLimit;
/**
* Allowed origins that can bypass Server Action's CSRF check. This is helpful
* when you have reverse proxy in front of your app.
* @example
* ["my-app.com"]
*/
allowedOrigins?: string[];
};
my next.confg.ts
for reference:
/** @type {import('next').NextConfig} */
const nextConfig = {
async rewrites() {
return process.env.NODE_ENV !== 'production' ? [
{
source: '/api/:path*',
destination: 'http://localhost:8000/:path*',
},
{
source: '/media/:path*',
destination: 'http://localhost:9000/:path*',
},
] : []
},
images: {
remotePatterns: process.env.NODE_ENV !== 'production' ? [
{
protocol: 'http',
hostname: 'localhost',
port: '',
pathname: '/media/**',
},
] : [
{
protocol: 'https',
hostname: 'qa.myappdomain.au',
port: '',
pathname: '/media/**',
},
],
},
experimental: {
serverActions: {
allowedOrigins: [
'http://localhost',
'https://qa.myappdomain.au/api',
]
}
}
}
Issue also happens for me when running next dev
on a local machine if you access app not via localhost but through another address in local network, like 192.168.1.2:3000
. Works without errors on 14.0.1.
I've had the same issue with nginx. I tried using serverActions.allowedOrigins and also experimental.serverActions.allowedOrigins / allowedHosts but didn't help. Downgrading to 14.0.1 fixed the problem. Hope the next update fix this.
Downgrading to v14.0.0 makes it work.
This also doesn't work for me w/ version > 14.0.1. Using Nx as well.
This also doesn't work for me w/ version > 14.0.1. Using Nx as well.
My two cents worth on another thread on the same issue.
The same here after update to 14.0.2
x-forwarded-host` header with value `localhost:3000` does not match `origin` header with
value `1xx.2xx.1xx.71:3000` from a forwarded Server Actions request. Aborting the action.
⨯ Error: Invalid Server Actions request.
at AsyncLocalStorage.run (node:async_hooks:338:14)
at AsyncLocalStorage.run (node:async_hooks:338:14)
This will be fixed by #58500
This is fixed in 14.0.3, upgrade to next 14.0.3 and the error will be gone.
Confirmed this is now solved in version 14.0.3.
Thanks all!
@TotomInc
I came across this issue ; upgrading to 14.0.3 did not solve it for me, but downgrading to 14.0.0 did. The POST request goes through when I replay it with all the X-Forwarded-**** headers removed. You can find the problematic headers below to reproduce the error :
X-Forwarded-For: 10.47.104.50
X-Forwarded-For: 34.159.59.249
X-Forwarded-Host: waba-stack-01.360dialog.io
X-Forwarded-Port: 443
X-Forwarded-Proto: https
X-Forwarded-Proto: https
X-Forwarded-Scheme: https
@TotomInc
I came across this issue ; upgrading to 14.0.3 did not solve it for me, but downgrading to 14.0.0 did. The POST request goes through when I replay it with all the X-Forwarded-**** headers removed. You can find the problematic headers below to reproduce the error :
X-Forwarded-For: 10.47.104.50 X-Forwarded-For: 34.159.59.249 X-Forwarded-Host: waba-stack-01.360dialog.io X-Forwarded-Port: 443 X-Forwarded-Proto: https X-Forwarded-Proto: https X-Forwarded-Scheme: https
same here. not fixed in 14.0.3.
confirmed, it fixed in 14.0.3
This doesn't work if using a custom port. I'm still getting the following in 14.0.3:
x-forwarded-host
header with value 213.xxx.xxx.xxx
does not match origin
header with value 213.xxx.xxx.xxx:3003
from a forwarded Server Actions request. Aborting the action.
14.0.3 Not fixed for us too.
We deploy on k8s and nextjs is behind nginx, and we get this. Is there a way to disable this behaviour for now?
`x-forwarded-host` header with value `SERVICE_NAME` does not match `origin` header with value `subdomain.domain.com` from a forwarded Server Actions request. Aborting the action.
I fixed this on my end by just returning from nginx whatever next.js expected instead of the standard $proxy_add_x_forwarded_for
I fixed this on my end by just returning from nginx whatever next.js expected instead of the standard
$proxy_add_x_forwarded_for
like this?
...
http {
upstream frontend-main {
server frontend-main;
}
server {
listen 80;
server_name subdomain.domain.com;
location / {
proxy_set_header x-forwarded-host "subdomain.domain.com";
proxy_pass http://frontend-main;
}
}
}
Yes, on my end it looks like this: proxy_set_header X-Forwarded-Host $host:3003
Yes, on my end it looks like this:
proxy_set_header X-Forwarded-Host $host:3003
confirmed working but I think it's hacky
I'm working on 14.0.3 in Codespaces and still getting error:
`x-forwarded-host` header with value `psychic-umbrella-xr45956gpg9crrw-3000.app.github.dev` does not match `origin` header with value `localhost:3000` from a forwarded Server Actions request. Aborting the action.
⨯ Error: Invalid Server Actions request.
nextjs.config.ts
const nextConfig = {
experimental: {
serverActions: {
allowedOrigins: ["https://psychic-umbrella-xr45956gpg9crrw-3000.app.github.dev"]
},
}
}
module.exports = nextConfig
14.0.3 Not fixed for us too.
Same for me - not fixed.
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
This fixed it for me
proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Proto $scheme;
This fixed it for me
this also resolves https://github.com/vercel/next.js/issues/58914
Shortest possible answer: So in my case I have a NextJS app running on localhost:3001. I have the environment configured to say NEXT_AUTH_URL and NEXT_AUTH_INTERNAL url are http://localhost:3001. BUT no matter what I do the form that nextauth generates always generates the following. I have a custom oauth provider configured, and when the default login form generates it generates a form with action
`<form action="http://localhost:3000/api/auth/signin/myprovider" method="POST"><input type="hidden" name="csrfToken" value="04dce1c0af8b3e0587a3db23c3d213110796a6348383a670295f683887162314"><input type="hidden" name="callbackUrl" value="http://localhost:3000"><button type="submit" class="button"><span>Sign in with MyProvider</span></button></form>`
So this is a bug, because the actual action should be pointing to localhost:3001 which is where the app is running. In my case I ALSO have a nextjs app running on localhost:3000. So some of my comments below may be unjustified except the part about clarifying the code and error messages perhaps. I think the offending code is here:
https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/react.tsx#L57
baseUrl and basePath do not honor the NEXTAUTH_URL_INTERNAL as they should so when the login form generates it gets a default unless NEXTAUTH_URL is configured, after configuring NEXTAUTH_URL stopping and starting the app and doing a full browser refresh, the form has the right url now.
Short answer: Whoever wrote the check in the code that contains the following string "One of these is needed to compare the 'origin' header from a forwarded Server Actions request...' was confused about the role of the x-forwarded-host header. There is NEVER any reason to compare the x-forwarded-host header with the origin header, to assert equality. The former identifies the server to whom a request is being made, and the latter identifies the client making the request. NOW it would make sense to compare the origin header with the x-forwarded-for header, but not the header currently being compared.
I am pretty sure the offending code is here: https://github.com/vercel/next.js/blob/9f69766cfa3234ac7a343abdd81f41315cf33891/packages/next/src/server/app-render/action-handler.ts#L324
It seems like this code is trying to mitigate a CSRF attack, but it relies strictly on host for comparison and not on host:port, which breaks things when you are running two different services in the same domain but on different ports.
So perhaps the intent was to ensure that either we set allowed origins as is called out in these lines of code: if (serverActions?.allowedOrigins?.includes(originDomain)) { // Ignore it } OR falling through to the case where you are checkng host == origindomain e.g. you are sending request to self. If that was the intent then perhaps a much more meaningful error response is in order. This is a very muddy piece of code and could use some clarification. allowed origins makes sense in the context of an Ajax request, but it does not make sense in the case where we are using NextAuth to send a post request from a login form. CORS does not apply to ordinary post requests.
Long answer: So looking through the latest updates next 14.0.2, in the next auth modules there are a whole lot of places where the string "https://localhost:3000" appears usually as a default. I think that somewhere in this stack trace the culprit lies if anyone is interested in maintaining the code to locate and address root cause. My case I am getting this by invoking a request from one NextJS app running on localhost port 3001 directing to another NextJS app running on localhost port 3000, the app on 3000 throws the error. The server running on port 3001, is running nextjs 14.0.3, and the one thing I do see is that when it sends a post request to the other server the Host header is localhost:3000, whereas the origin and referrer are localhost:3001. I suspect that somewhere in the call stack below, the Host header is somehow being conflated with x-forwarded for header perhaps.
forwarded Server Actions request. Aborting the action. ⨯ Error: Invalid Server Actions request. at tX (C:\Users\someone\source\my-server-project\node_modules\next\dist\compiled\next-server\app-page.runtime.dev.js:37:4749) at rl (C:\Users\someone\source\my-server-project\node_modules\next\dist\compiled\next-server\app-page.runtime.dev.js:38:23000) at C:\Users\someone\source\my-server-project\node_modules\next\dist\compiled\next-server\app-page.runtime.dev.js:38:25299 at AsyncLocalStorage.run (node:async_hooks:346:14) at Object.wrap (C:\Users\someone\source\my-server-project\node_modules\next\dist\compiled\next-server\app-page.runtime.dev.js:35:423547) at C:\Users\someone\source\my-server-project\node_modules\next\dist\compiled\next-server\app-page.runtime.dev.js:38:25187 at AsyncLocalStorage.run (node:async_hooks:346:14) at Object.wrap (C:\Users\someone\source\my-server-project\node_modules\next\dist\compiled\next-server\app-page.runtime.dev.js:35:423074) at ru (C:\Users\someone\source\my-server-project\node_modules\next\dist\compiled\next-server\app-page.runtime.dev.js:38:25114) at rO.render (C:\Users\someone\source\my-server-project\node_modules\next\dist\compiled\next-server\app-page.runtime.dev.js:38:29210) at doRender (C:\Users\someone\source\my-server-project\node_modules\next\dist\server\base-server.js:1407:44) at cacheEntry.responseCache.get.routeKind (C:\Users\someone\source\my-server-project\node_modules\next\dist\server\base-server.js:1571:34) at ResponseCache.get (C:\Users\someone\source\my-server-project\node_modules\next\dist\server\response-cache\index.js:54:26) at DevServer.renderToResponseWithComponentsImpl (C:\Users\someone\source\my-server-project\node_modules\next\dist\server\base-server.js:1479:53) at C:\Users\someone\source\my-server-project\node_modules\next\dist\server\base-server.js:983:121 at NextTracerImpl.trace (C:\Users\someone\source\my-server-project\node_modules\next\dist\server\lib\trace\tracer.js:102:20) at DevServer.renderToResponseWithComponents (C:\Users\someone\source\my-server-project\node_modules\next\dist\server\base-server.js:983:41) at DevServer.renderErrorToResponseImpl (C:\Users\someone\source\my-server-project\node_modules\next\dist\server\base-server.js:2104:35) at async pipe.req.req (C:\Users\someone\source\my-server-project\node_modules\next\dist\server\base-server.js:1982:30) at async DevServer.pipeImpl (C:\Users\someone\source\my-server-project\node_modules\next\dist\server\base-server.js:902:25) `x-f
So I dug a little more the actual error message was
x-forwarded-host
header with value localhost:3000
does not match origin
header with value localhost:3001
from a forwarded Server Actions request. Aborting the action.
⨯ Error: Invalid Server Actions request.
I scanned node-modules/next/dist/server/base-server.js and found the following lines of code in async handleRequestImpl(req, res, parsedUrl) {
starting at line 505
req.headers["x-forwarded-host"] ??= ${this.hostname}:${this.port}
;
req.headers["x-forwarded-port"] ??= (_this_port = this.port) == null ? void 0 : _this_port.toString();
const { originalRequest } = req;
req.headers["x-forwarded-proto"] ??= ((_originalRequest_socket = originalRequest.socket) == null ? void 0 : _originalRequest_socket.encrypted) ? "https" : "http";
req.headers["x-forwarded-for"] ??= (_originalRequest_socket1 = originalRequest.socket) == null ? void 0 : _originalRequest_socket1.remoteAddress;
This would explain a lot if the receiving app is running on host localhost:3000 and a request is made to it from another next app on localhost:3001, then the request header would have a host of localhost:3000. But then line 505 would set the x-forwarded-host to localhost:3000, which would later trigger the issue where the x-forwarded-host does not match the value of the origin host which is localhost:3001.
Looking at the next version 14.0.3 code line 505 now looks like this: req.headers["x-forwarded-host"] ??= req.headers["host"] ?? this.hostname; req.headers["x-forwarded-port"] ??= (_this_port = this.port) == null ? void 0 : _this_port.toString(); const { originalRequest } = req; req.headers["x-forwarded-proto"] ??= ((_originalRequest_socket = originalRequest.socket) == null ? void 0 : _originalRequest_socket.encrypted) ? "https" : "http"; req.headers["x-forwarded-for"] ??= (_originalRequest_socket1 = originalRequest.socket) == null ? void 0 : _originalRequest_socket1.remoteAddress;
But that change does not address the issue when you are testing two next apps running on localhost. So I don't think the issue has been resolved in 14.0.3. According to the HTTP specification The Host request header specifies the host and port number of the server to which the request is being sent. Whereas the The X-Forwarded-Host (XFH) header is a de-facto standard header for identifying the original host requested by the client in the Host HTTP. So the code in line 505 is correct, however the error that is being triggered is checking for a condition that it should not check. There is no reason ever why someone should be tryig to check to make sure that the x-forwarded-host matches the origin. The origin identifies the client service, and the x-forwarded-host is the server in the relationship. why is this error ever even being thrown?
In many places in the compiled JS under app-page-experimental.runtime.dev.js for example there is this snippet
from a forwarded Server Actions request. Aborting the action.):console.error("
x-forwarded-hostor
hostheaders are not provided. One of these is needed to compare the
origin` header from a forwarded Server Actions request. Aborting the action.");let e=Error("Invalid Server Actions request."
I think that whoever is responsible for that code was confused about the difference between what the x-forwarded-host header and the x-forwarded-for header are all about. The X-Forwarded-For (XFF) request header is a de-facto standard header for identifying the originating IP address of a client connecting to a web server through a proxy server. And so is the origin header in which case such a check would make sense but not the check that is currently happening in nextjs.
@TotomInc
I came across this issue ; upgrading to 14.0.3 did not solve it for me, but downgrading to 14.0.0 did. The POST request goes through when I replay it with all the X-Forwarded-**** headers removed. You can find the problematic headers below to reproduce the error :
X-Forwarded-For: 10.47.104.50 X-Forwarded-For: 34.159.59.249 X-Forwarded-Host: waba-stack-01.360dialog.io X-Forwarded-Port: 443 X-Forwarded-Proto: https X-Forwarded-Proto: https X-Forwarded-Scheme: https
Based on the fact that you have multiple x-forwarded-proto
headers, sounds like you might be running in to this issue.
For those that run nginx with proxy_set_header X-Forwarded-Proto ...
, nginx will strip out the multiple headers, so thats a possible workaround.
@lewisnewson #58019 (comment)
Fantastic, adding their recommendation of the following sorted the issue for me;
next.config.js
experimental.serverActions.allowedOrigins: ["https://nextjs-14-0-2-bug-report-4ea064a57303.herokuapp.com"]
So for those needing a hand with this, the above should work as fix.
Thanks for digging that out @JavierMartinz
Thanks!! It solve my problem.
I had the same problem on AWS ECS Fargate where I using nginx in side container as reverse proxy for Next.js.
Here is my template for nginx.config that is working great:
events {
worker_connections 1024;
}
http {
resolver 127.0.0.11 valid=30s ipv6=off;
# Nginx will allocate up to 16 buffers, each of size 32 KB, for each client connection.
# These buffers are used to temporarily store parts of the response from the upstream server before forwarding them to the client.
# Adjusting the number and size of buffers can impact the memory usage and performance of Nginx when serving large responses.
proxy_buffers 16 32k;
# Nginx configures a buffer of 64 KB to read the response from the upstream server.
# This buffer is used to temporarily store chunks of the response as it is being received.
# The size of this buffer can affect the efficiency of data transfer between Nginx and the upstream server.
# A larger buffer may reduce the number of read operations at the expense of increased memory usage.
proxy_buffer_size 64k;
# This passes the host header from the client to the SSL handshake with the proxied server
# Useful when the proxied server relies on Server Name Indication (SNI)
# This way, the proxied server can present the correct SSL certificate matching the client's host header
proxy_ssl_server_name on;
server {
listen 80;
listen [::]:80;
server_name ${DOMAIN_NAME};
# Home page
location = / {
proxy_pass ${WEB_FLOW_URL};
proxy_set_header Host ${WEB_FLOW_HOST};
}
# Other Webflow pages
location = /contact {
proxy_pass ${WEB_FLOW_URL}/contact;
proxy_set_header Host ${WEB_FLOW_HOST};
}
location = /pricing {
proxy_pass ${WEB_FLOW_URL}/pricing;
proxy_set_header Host ${WEB_FLOW_HOST};
}
location = /privacy {
proxy_pass ${WEB_FLOW_URL}/privacy;
proxy_set_header Host ${WEB_FLOW_HOST};
}
location = /cookies {
proxy_pass ${WEB_FLOW_URL}/cookies;
proxy_set_header Host ${WEB_FLOW_HOST};
}
location = /terms {
proxy_pass ${WEB_FLOW_URL}/terms;
proxy_set_header Host ${WEB_FLOW_HOST};
}
location = /jobs {
proxy_pass ${WEB_FLOW_URL}/jobs;
proxy_set_header Host ${WEB_FLOW_HOST};
}
# next.js folder with static assets
location /static/ {
expires 1d;
add_header Cache-Control "public, max-age=31536000, immutable";
}
# Everything else goes to next.js web app
location / {
proxy_pass ${NEXT_JS_URL};
proxy_http_version 1.1;
proxy_read_timeout 900;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
}
client_header_timeout 60;
client_body_timeout 60;
keepalive_timeout 60;
gzip on;
gzip_comp_level 4;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
}
}
and script to replace these variables in template:
#!/bin/sh
# this script is used to replace the environment variables in the nginx.conf file
# env variables are set by infrastructure, ECS Task Definition
# shellcheck disable=SC2016
envsubst '$DOMAIN_NAME $NEXT_JS_URL $WEB_FLOW_HOST $WEB_FLOW_URL' < /etc/nginx/template.nginx.conf > /etc/nginx/nginx.conf
exec nginx -g 'daemon off;'
dockerfile
FROM nginx:alpine
# needed to use envsubst in start-nginx.sh
RUN apk add busybox-extras
# Copy the Nginx config
COPY template.nginx.conf /etc/nginx/template.nginx.conf
COPY start-nginx.sh /start-nginx.sh
RUN chmod +x /start-nginx.sh
# Forward request logs to Docker log collector
RUN ln -sf /dev/stdout /var/log/nginx/access.log \
&& ln -sf /dev/stderr /var/log/nginx/error.log
EXPOSE 80
CMD ["/start-nginx.sh"]
env variables in task definition:
environment = [
{
name = "DOMAIN_NAME",
value = "www.domain.tld"
},
{
name = "NEXT_JS_URL",
value = "http://localhost:3000"
},
{
name = "WEB_FLOW_HOST",
value = "webflow.domain.tld"
},
{
name = "WEB_FLOW_URL",
value = "https://webflow.domain.tld"
}
],
i'm on v14.0.3 and using this works for me.
on next.config.js
serverActions: { allowedOrigins: ["xxxx.com", "localhost:3001"], }
ps. do not add HTTP or https on origins
This closed issue has been automatically locked because it had no new activity for 2 weeks. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.
Link to the code that reproduces this issue
https://github.com/lewisnewson/nextjs-14.0.2-bug-report
To Reproduce
main
branch onto a basic Heroku dynoCurrent vs. Expected behavior
Currently:
x-forwarded-host
header with valuelocalhost:23380
does not matchorigin
header with valuenextjs-14-0-2-bug-report-4ea064a57303.herokuapp.com
from a forwarded Server Actions request. Aborting the action. -- Error: Invalid Server Actions request."What should happen:
Verify canary release
Provide environment information
Which area(s) are affected? (Select all that apply)
App Router
Additional context
Since upgrading from 14.0.1 to 14.0.2, deployments into a Heroku environment have now seen issues arise with the use of server actions. This can be shown in the example provided, when deployed to Heroku, loading the application prints the error message into the console of the dyno.
This issue does not happen when running
next dev
on a local machine, instead it only seems to appear when the application is deployed into production.