[GH-ISSUE #6257] Origin Header Issue #10461

Closed
opened 2026-04-13 06:37:30 -05:00 by GiteaMirror · 5 comments
Owner

Originally created by @milhim on GitHub (Nov 24, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/6257

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Setup the Project With NodeJS and Fastify
  2. Enable Email & Password Auth
  3. Add trusted origin
  4. Send Signup request from postman

Current vs. Expected behavior

Current :
I send first request to signup with postman without the Origin header it pass without any issue and the account was created but the second request response was NULL_OR_MISSING_ORIGIN

Expected:
All request must response with NULL_OR_MISSING_ORIGIN If I did not attach the origin header

What version of Better Auth are you using?

^1.3.34

System info

I did not run this command I added the config below

Which area(s) are affected? (Select all that apply)

Backend

Auth config (if applicable)

import { betterAuth } from "better-auth"
export const auth = betterAuth({
  emailAndPassword: {  
    enabled: true
  },
});

Additional context

/* eslint-disable sonarjs/todo-tag /
/
eslint-disable @typescript-eslint/no-unused-vars /
/
eslint-disable no-unused-vars */
import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";

import prisma from "./prisma.js";
import { env } from "../config/env.js";
import { passkey } from "better-auth/plugins/passkey";
import { captcha, magicLink, twoFactor } from "better-auth/plugins";
import { referralPlugin } from "./plugins/index.js";
import {
BETTER_AUTH_APP_NAMES,
CAPTCHA_PROVIDER,
MAGIC_LINK_EXPIRES_IN,
PASS_KEY_ORIGIN,
PASS_KEY_RP_ID,
PASS_KEY_RP_NAME,
TRUSTED_ORIGINS,
} from "../constants/index.js";

export const auth = betterAuth({
secret: env.BETTER_AUTH_SECRET,
database: prismaAdapter(prisma, {
provider: "postgresql",
}),
appName: BETTER_AUTH_APP_NAMES,
emailAndPassword: {
enabled: true,
minPasswordLength: 8,
},
],
trustedOrigins: TRUSTED_ORIGINS,
});
export const TRUSTED_ORIGINS = [
"http://localhost:3000",
"http://localhost:5000",
];

Originally created by @milhim on GitHub (Nov 24, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/6257 ### Is this suited for github? - [ ] Yes, this is suited for github ### To Reproduce 1. Setup the Project With NodeJS and Fastify 2. Enable Email & Password Auth 3. Add trusted origin 4. Send Signup request from postman ### Current vs. Expected behavior Current : I send first request to signup with postman without the Origin header it pass without any issue and the account was created but the second request response was NULL_OR_MISSING_ORIGIN Expected: All request must response with NULL_OR_MISSING_ORIGIN If I did not attach the origin header ### What version of Better Auth are you using? ^1.3.34 ### System info ```bash I did not run this command I added the config below ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" export const auth = betterAuth({ emailAndPassword: { enabled: true }, }); ``` ### Additional context /* eslint-disable sonarjs/todo-tag */ /* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable no-unused-vars */ import { betterAuth } from "better-auth"; import { prismaAdapter } from "better-auth/adapters/prisma"; import prisma from "./prisma.js"; import { env } from "../config/env.js"; import { passkey } from "better-auth/plugins/passkey"; import { captcha, magicLink, twoFactor } from "better-auth/plugins"; import { referralPlugin } from "./plugins/index.js"; import { BETTER_AUTH_APP_NAMES, CAPTCHA_PROVIDER, MAGIC_LINK_EXPIRES_IN, PASS_KEY_ORIGIN, PASS_KEY_RP_ID, PASS_KEY_RP_NAME, TRUSTED_ORIGINS, } from "../constants/index.js"; export const auth = betterAuth({ secret: env.BETTER_AUTH_SECRET, database: prismaAdapter(prisma, { provider: "postgresql", }), appName: BETTER_AUTH_APP_NAMES, emailAndPassword: { enabled: true, minPasswordLength: 8, }, ], trustedOrigins: TRUSTED_ORIGINS, }); export const TRUSTED_ORIGINS = [ "http://localhost:3000", "http://localhost:5000", ];
GiteaMirror added the locked label 2026-04-13 06:37:30 -05:00
Author
Owner

@better-auth-agent[bot] commented on GitHub (Nov 24, 2025):

(tag @better-auth-agent if you would like an answer from the Agent)

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3569387171 --> @better-auth-agent[bot] commented on GitHub (Nov 24, 2025): (tag @better-auth-agent if you would like an answer from the Agent) <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) • [Discord](https://discord.gg/better-auth) • [GitHub](https://github.com/better-auth/better-auth) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/better-auth) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/better-auth/better-auth)
Author
Owner

@milhim commented on GitHub (Nov 24, 2025):

@better-auth-agent

<!-- gh-comment-id:3569388306 --> @milhim commented on GitHub (Nov 24, 2025): @better-auth-agent
Author
Owner

@better-auth-agent[bot] commented on GitHub (Nov 24, 2025):

It looks like your setup is encountering an inconsistent Origin header validation: the first request without an Origin header passes while subsequent calls return a NULL_OR_MISSING_ORIGIN error. This behavior can occur if the trusted origins aren’t being enforced uniformly. I suggest double-checking that your trusted origins array (in your case, [ "http://localhost:3000", "http://localhost:5000" ]) is correctly configured and that all requests from Postman include the Origin header—even if it seems redundant. It’s also worth reviewing whether your Fastify setup or any middleware is altering the header between requests. Have you tried explicitly sending an Origin header on each request to see if the behavior becomes consistent? Tag @better-auth-agent if you would like an answer from the Agent.

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3569389251 --> @better-auth-agent[bot] commented on GitHub (Nov 24, 2025): It looks like your setup is encountering an inconsistent Origin header validation: the first request without an Origin header passes while subsequent calls return a NULL_OR_MISSING_ORIGIN error. This behavior can occur if the trusted origins aren’t being enforced uniformly. I suggest double-checking that your trusted origins array (in your case, [ "http://localhost:3000", "http://localhost:5000" ]) is correctly configured and that all requests from Postman include the Origin header—even if it seems redundant. It’s also worth reviewing whether your Fastify setup or any middleware is altering the header between requests. Have you tried explicitly sending an Origin header on each request to see if the behavior becomes consistent? Tag @better-auth-agent if you would like an answer from the Agent. <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) • [Discord](https://discord.gg/better-auth) • [GitHub](https://github.com/better-auth/better-auth) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/better-auth) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/better-auth/better-auth)
Author
Owner

@dosubot[bot] commented on GitHub (Nov 24, 2025):

Hi @milhim! I'm Dosu and I’m helping the better-auth team.

Better Auth v1.3.34 enforces strict Origin header checks for POST requests that use cookies. If a request is missing the Origin (or Referer) header, it will return a 403 NULL_OR_MISSING_ORIGIN error—this is intentional for CSRF protection and was introduced in v1.3.29+ (source). This strict enforcement can break flows in environments like Postman, which does not send an Origin header by default.

The reason your first request without an Origin header succeeds but subsequent requests fail is likely due to session or cookie state: the origin check only applies when cookies are present, so the first request may create a session, and subsequent requests with cookies trigger the strict check (middleware code).

To ensure all requests without an Origin header are rejected, you should always test with cookies enabled. If you need to bypass this check for development or testing (e.g., with Postman), you can set advanced: { disableOriginCheck: true } in your Better Auth config, but this disables CSRF protection and should only be used in isolated dev environments (workaround). For production, always include the Origin header in your requests and make sure your trustedOrigins config matches the expected origins.

If you want to debug further, log the actual Origin and Referer headers received by your backend to see what is being sent from your client (debug tip).

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Chat with Dosu Open in Cursor Join Discord Share on X

<!-- gh-comment-id:3569400104 --> @dosubot[bot] commented on GitHub (Nov 24, 2025): <!-- Greeting --> Hi @milhim! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> Better Auth v1.3.34 enforces strict Origin header checks for POST requests that use cookies. If a request is missing the Origin (or Referer) header, it will return a 403 NULL_OR_MISSING_ORIGIN error—this is intentional for CSRF protection and was introduced in v1.3.29+ ([source](https://github.com/better-auth/better-auth/issues/5573)). This strict enforcement can break flows in environments like Postman, which does not send an Origin header by default. The reason your first request without an Origin header succeeds but subsequent requests fail is likely due to session or cookie state: the origin check only applies when cookies are present, so the first request may create a session, and subsequent requests with cookies trigger the strict check ([middleware code](https://github.com/better-auth/better-auth/blob/fc662c52dcadc9874d36bd354d53d23f8a24e098/packages/better-auth/src/api/middlewares/origin-check.ts#L12-L92)). To ensure all requests without an Origin header are rejected, you should always test with cookies enabled. If you need to bypass this check for development or testing (e.g., with Postman), you can set `advanced: { disableOriginCheck: true }` in your Better Auth config, but this disables CSRF protection and should only be used in isolated dev environments ([workaround](https://github.com/better-auth/better-auth/issues/5573)). For production, always include the Origin header in your requests and make sure your trustedOrigins config matches the expected origins. If you want to debug further, log the actual Origin and Referer headers received by your backend to see what is being sent from your client ([debug tip](https://github.com/better-auth/better-auth/issues/5536)). <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/88098536-c6b8-46db-ac4a-5619614fe037?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/88098536-c6b8-46db-ac4a-5619614fe037?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/88098536-c6b8-46db-ac4a-5619614fe037?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/88098536-c6b8-46db-ac4a-5619614fe037?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/88098536-c6b8-46db-ac4a-5619614fe037?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/88098536-c6b8-46db-ac4a-5619614fe037?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/88098536-c6b8-46db-ac4a-5619614fe037?feedback_type=other)</sup> [![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=Better%20Auth%20v1.3.34%20enforces%20strict%20Origin%20header%20checks%20for%20POST%20requests%20that%20use%20cookies.%20If%20a%20request%20is%20missing%20the%20Origin%20%28or%20Referer%29%20header%2C%20it%20will%20return%20a%20403%20NULL_OR_MISSING_ORIGIN%20error%E2%80%94this%20is%20intentional%20for%20CSRF%20protection%20and%20was%20introduced%20in%20v1.3.29%2B%20%28%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/issues/5573%29%29.%20This%20strict%20enforcement%20can%20break%20flows%20in%20environments%20like%20Postman%2C%20which%20does%20not%20send%20an%20Origin%20header%20by%20default.%0A%0AThe%20reason%20your%20first%20request%20without%20an%20Origin%20header%20succeeds%20but%20subsequent%20requests%20fail%20is%20likely%20due%20to%20session%20or%20cookie%20state%3A%20the%20origin%20check%20only%20applies%20when%20cookies%20are%20present%2C%20so%20the%20first%20request%20may%20create%20a%20session%2C%20and%20subsequent%20requests%20with%20cookies%20trigger%20the%20strict%20check%20%28%5Bmiddleware%20code%5D%28https%3A//github.com/better-auth/better-auth/blob/fc662c52dcadc9874d36bd354d53d23f8a24e098/packages/better-auth/src/api/middlewares/origin-check.ts%23L12-L92%29%29.%0A%0ATo%20ensure%20all%20requests%20without%20an%20Origin%20header%20are%20rejected%2C%20you%20should%20always%20test%20with%20cookies%20enabled.%20If%20you%20need%20to%20bypass%20this%20check%20for%20development%20or%20testing%20%28e.g.%2C%20with%20Postman%29%2C%20you%20can%20set%20%60advanced%3A%20%7B%20disableOriginCheck%3A%20true%20%7D%60%20in%20your%20Better%20Auth%20config%2C%20but%20this%20disables%20CSRF%20protection%20and%20should%20only%20be%20used%20in%20isolated%20dev%20environments%20%28%5Bworkaround%5D%28https%3A//github.com/better-auth/better-auth/issues/5573%29%29.%20For%20production%2C%20always%20include%20the%20Origin%20header%20in%20your%20requests%20and%20make%20sure%20your%20trustedOrigins%20config%20matches%20the%20expected%20origins.%0A%0AIf%20you%20want%20to%20debug%20further%2C%20log%20the%20actual%20Origin%20and%20Referer%20headers%20received%20by%20your%20backend%20to%20see%20what%20is%20being%20sent%20from%20your%20client%20%28%5Bdebug%20tip%5D%28https%3A//github.com/better-auth/better-auth/issues/5536%29%29.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/6257)
Author
Owner

@Paola3stefania commented on GitHub (Jan 11, 2026):

Hi @milhim

Thanks for the detailed report :)
After investigating the codebase, this is actually working as designed rather than a bug.

How Origin Validation Works

Better Auth only validates the Origin header when cookies are present in the request. This is intentional CSRF protection logic:

const useCookies = headers.has("cookie");
const shouldValidate =
    forceValidate ||
    (useCookies && !ctx.context.skipCSRFCheck && !ctx.context.skipOriginCheck);

if (!shouldValidate) {
    return; // Skip origin validation
}

What's Happening in Your Case

Request Cookies Origin Header Result
1st signup None (no session yet) Missing Succeeds (origin check skipped)
2nd request Session cookie from 1st request Missing MISSING_OR_NULL_ORIGIN

Postman preserves cookies between requests by default, so after the first successful signup, subsequent requests include the session cookie — triggering the origin validation

The concept behind this

  1. Browsers always send cookies → CSRF protection is needed
  2. API clients (mobile apps, CLI tools, Postman) often use bearer tokens instead of cookies → no CSRF risk without cookies
  3. First-time requests have no session → safe to skip validation

Workarounds

  1. Add an Origin header in Postman matching your trustedOrigins (e.g., http://localhost:3000)
  2. Clear cookies in Postman between requests (or disable cookie persistence)
  3. Use bearer token authentication instead of cookie-based sessions

Hope this clarifies things 😊

<!-- gh-comment-id:3733764475 --> @Paola3stefania commented on GitHub (Jan 11, 2026): Hi @milhim Thanks for the detailed report :) After investigating the codebase, this is actually **working as designed** rather than a bug. ## How Origin Validation Works Better Auth only validates the `Origin` header **when cookies are present** in the request. This is intentional CSRF protection logic: ```typescript const useCookies = headers.has("cookie"); const shouldValidate = forceValidate || (useCookies && !ctx.context.skipCSRFCheck && !ctx.context.skipOriginCheck); if (!shouldValidate) { return; // Skip origin validation } ``` ## What's Happening in Your Case | Request | Cookies | Origin Header | Result | |---------|---------|---------------|--------| | 1st signup | None (no session yet) | Missing | Succeeds (origin check skipped) | | 2nd request | Session cookie from 1st request | Missing | `MISSING_OR_NULL_ORIGIN` | Postman preserves cookies between requests by default, so after the first successful signup, subsequent requests include the session cookie — triggering the origin validation ## The concept behind this 1. **Browsers always send cookies** → CSRF protection is needed 2. **API clients (mobile apps, CLI tools, Postman)** often use bearer tokens instead of cookies → no CSRF risk without cookies 3. **First-time requests** have no session → safe to skip validation ## Workarounds 1. **Add an Origin header** in Postman matching your `trustedOrigins` (e.g., `http://localhost:3000`) 2. **Clear cookies** in Postman between requests (or disable cookie persistence) 3. **Use bearer token authentication** instead of cookie-based sessions Hope this clarifies things 😊
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#10461