payloadcms / payloadcms/payload
Cookie auth rejects same-origin requests over plain HTTP (regression in 3.79.1)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 44.8k
- Forks
- 4.2k
- Avg merge
- 2d 21h
- Merged PRs (30d)
- 53
Description
Describe the Bug
Since 3.79.1 a valid payload-token cookie is discarded when the request carries neither Origin nor Sec-Fetch-Site. That is what a browser sends for a same-origin navigation over plain HTTP: Origin is omitted for navigations, and Sec-Fetch-* goes only to potentially trustworthy origins] (see W3 spec), which http:// on a non-localhost host is not.
Effect: login returns 200 and sets the cookie, /admin bounces straight back to /admin/login.
Local development is commonly served over plain HTTP under a hostname rather than localhost, which is where this bites — Docksal, DDEV, Lando, or a plain reverse proxy in front of the dev server.
What changed in dist/auth/extractJWT.js, cookie strategy. 3.79.0 accepted a
missing Origin:
if (!origin || payload.config.csrf.length === 0 || payload.config.csrf.indexOf(origin) > -1) {
return cookieToken
}
3.79.1 requires Sec-Fetch-Site instead, and returns null without it.
csrf: [] is not a way out: dist/config/sanitize.js does config.csrf.push(config.serverURL), so the csrf.length === 0 branch is unreachable
for anyone who sets serverURL.
Link to the code that reproduces this issue
https://github.com/fullheart/payload-issue-17565
Reproduction Steps
See README: https://github.com/fullheart/payload-issue-17565/blob/main/README.md#run-it
Which area(s) are affected?
area: core
Environment Info
Binaries:
Node: 24.18.1
npm: 11.16.0
Relevant Packages:
payload: 3.86.0
next: 16.2.12
@payloadcms/db-postgres: 3.86.0
react: 19.2.8
react-dom: 19.2.8
Operating System:
Platform: linux
Arch: x64
Workaround
Via patch-package — treat an absent Sec-Fetch-Site as "the client did not say" rather than "cross-site", and fall back to the Referer origin, still checked against the same csrf allowlist:
if (secFetchSite === 'same-origin' || secFetchSite === 'same-site' || secFetchSite === 'none') {
return cookieToken;
}
+ if (!secFetchSite) {
+ const referer = headers.get('Referer');
+ if (referer) {
+ try {
+ if (payload.config.csrf.includes(new URL(referer).origin)) {
+ return cookieToken;
+ }
+ } catch {}
+ }
+ }
// Reject cross-site requests and missing header (non-browser clients)
return null;
UPDATE: Is this related #16031?
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in dist/auth/extractJWT.js and dist/config/sanitize.js, then run the README reproduction from payload-issue-17565. Verify that a valid cookie works for same-origin plain HTTP requests without Origin or Sec-Fetch-Site while the configured CSRF allowlist still rejects cross-site requests; add or update coverage for the regression if the repository’s relevant tests are located.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- authentication, backend, security
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 58/100