CSRF token is missing or invalid.
Self Checks
- [x] I have read the Contributing Guide and Language Policy.
- [x] This is only for bug report, if you would like to ask a question, please head to Discussions.
- [x] I have searched for existing issues search for existing issues, including closed ones.
- [x] I confirm that I am using English to submit this report, otherwise it will be closed.
- [x] 【中文用户 & Non English User】请使用英语提交,否则会被关闭 :)
- [x] Please do not modify this template :) and fill in all the required fields.
Dify version
1.10.0
Cloud or Self Hosted
Self Hosted (Docker)
Steps to reproduce
Our self hosted dify is using different subdomains for CONSOLE_WEB_URL (https://agent.myhost.com) and CONSOLE_API_URL(https://api.agent.myhost.com).
After upgrading to 1.9.2 and now 1.10.0, this issue has not been resolved yet.
I updated env vars below in the .env file after upgrading to 1.10.0:
COOKIE_DOMAIN=agent.myhost.com
NEXT_PUBLIC_COOKIE_DOMAIN=agent.myhost.com
Steps to reproduce:
1、 Open a new Chrome window in incognito mode.
2、 access the CONSOLE_WEB_URL and login, login was succeed.
3、 many XHR requests got 401 status code and the body message shows :
{
"code": "unauthorized",
"message": "CSRF token is missing or invalid.",
"status": 401
}
4、However, the csrf_token could be found in the cookie of the request header.
Here is an cURL request copied from browser request log(I only mannually changed the domain for privacy reason):
$: curl 'https://api.agent.myhost.com/console/api/workspaces' \
-H 'accept: */*' \
-H 'accept-language: zh-CN,zh;q=0.9' \
-H 'content-type: application/json' \
-b 'access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiNzM4MWYyZjQtYTI2ZC00NmVmLWJhN2EtM2MzODIzZTY2MmZjIiwiZXhwIjoxNzYzMTEyNjcyLCJpc3MiOiJTRUxGX0hPU1RFRCIsInN1YiI6IkNvbnNvbGUgQVBJIFBhc3Nwb3J0In0.e2TkPiH2TVDpoZ9uptzmemNL34N9gS9rqDbnhAm7gvY; refresh_token=fb5c35ea4387d4b645b1e3fd46d1b8fe745662fe33d3d531bf5ce223418ca01b7da7cb3f5fe4fe6edb791d24e1444a605aba63b293b845e50179cbf63dfd0457; csrf_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NjMxMTI2NzIsInN1YiI6IjczODFmMmY0LWEyNmQtNDZlZi1iYTdhLTNjMzgyM2U2NjJmYyJ9.AbgdktmbPpRXi8Pun-lKsRidsrlV3iQ6rwg6qM95upg' \
-H 'origin: https://agent.myhost.com' \
-H 'priority: u=1, i' \
-H 'referer: https://agent.myhost.com/' \
-H 'sec-ch-ua: "Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"' \
-H 'sec-ch-ua-mobile: ?0' \
-H 'sec-ch-ua-platform: "macOS"' \
-H 'sec-fetch-dest: empty' \
-H 'sec-fetch-mode: cors' \
-H 'sec-fetch-site: same-site' \
-H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36' \
-H 'x-csrf-token;'
{
"code": "unauthorized",
"message": "CSRF token is missing or invalid.",
"status": 401
}
✔️ Expected Behavior
The API requests should be successful.
❌ Actual Behavior
The API requests return 401 unauthorized.
Your CSRF validation issue is likely due to two things: the cookie domain configuration and how the CSRF token is sent in requests.
-
COOKIE_DOMAIN should be set to the parent domain (not a subdomain). For your setup, use
COOKIE_DOMAIN=myhost.com(or.myhost.com, both are accepted) in both your API and web.envfiles. This allows cookies (includingcsrf_token) to be shared betweenagent.myhost.comandapi.agent.myhost.comas required for cross-subdomain authentication. Setting it toagent.myhost.comrestricts the cookie to that subdomain only, which breaks cross-subdomain requests. See the usage notes in the latest.env.examplefiles and recent PRs. -
The CSRF token must be sent in the
X-CSRF-TOKENheader as well as in the cookie. Dify's backend checks the header, not just the cookie. If you are making requests manually (e.g., with cURL), you must include the header:-H 'X-CSRF-TOKEN: <your_csrf_token_value>'The frontend should handle this automatically, but if you are customizing requests or using tools, double-check that the header is present. If the header is missing, you will get a 401 error even if the cookie is set (details, code).
-
CORS settings: Make sure your CORS configuration allows credentials and the
X-CSRF-TOKENheader, but this should be handled by default in recent Dify versions (see here).
Checklist:
- Set
COOKIE_DOMAIN=myhost.com(not a subdomain) in both API and web.envfiles. - Set
NEXT_PUBLIC_COOKIE_DOMAIN=myhost.comin the web.envfile. - Restart all containers after changing
.env. - Confirm that requests include both the
csrf_tokencookie and theX-CSRF-TOKENheader.
This should resolve the CSRF 401 errors in your cross-subdomain Dify deployment.
To reply, just mention @dosu.
How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other
I'm running this on Railway and noticed that the environment variables have to be available during build time in the Dockerfile:
ARG NEXT_PUBLIC_COOKIE_DOMAIN
ARG NEXT_PUBLIC_API_PREFIX
ARG NEXT_PUBLIC_PUBLIC_API_PREFIX
ARG NEXT_PUBLIC_MARKETPLACE_API_PREFIX
ENV NEXT_PUBLIC_COOKIE_DOMAIN=${NEXT_PUBLIC_COOKIE_DOMAIN}
ENV NEXT_PUBLIC_API_PREFIX=${NEXT_PUBLIC_API_PREFIX}
ENV NEXT_PUBLIC_PUBLIC_API_PREFIX=${NEXT_PUBLIC_PUBLIC_API_PREFIX}
ENV NEXT_PUBLIC_MARKETPLACE_API_PREFIX=${NEXT_PUBLIC_MARKETPLACE_API_PREFIX}
Had to build it locally and push it to docker to make it work as a quick fix:
$ docker buildx create --name dify-builder --use # run once
$ docker buildx build \
--platform linux/amd64 \
-t <docker-user>/dify-web:cookie-domain \
--build-arg NEXT_PUBLIC_COOKIE_DOMAIN=... \
--build-arg NEXT_PUBLIC_API_PREFIX=... \
--build-arg NEXT_PUBLIC_PUBLIC_API_PREFIX=... \
--build-arg NEXT_PUBLIC_MARKETPLACE_API_PREFIX=... \
-f web/Dockerfile web \
--push
The solution Dosu proposed requires a rebuild.
Unset the COOKIE_DOMAIN environment variable on the API side.
When it is set, the API issues a csrf-token cookie, but the frontend attempts to read __Host-csrf_token.
Rebuilding would make the frontend read csrf-token, but if you want to avoid a rebuild, unsetting COOKIE_DOMAIN will resolve the issue.
COOKIE_DOMAIN=
If COOKIE_DOMAIN is unset, both the frontend and backend will read the __Host-csrf_token cookie.
I got the same problem, but I only set the CONSOLE_API_URL to my custom host.