Sign up requests from the server... just hang & time out? #2627

Closed
opened 2026-03-13 10:09:14 -05:00 by GiteaMirror · 8 comments
Owner

Originally created by @karam94 on GitHub (Dec 29, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Framework: Tsed.dev using Express v5

Server.ts (what's relevant):

	public $beforeRoutesInit(): void {
		console.log("ENTERED SERVER");
		this.app.use(
			cors({
				origin: this.config.urls.frontend,
				credentials: true
			})
		);

		this.app.use(RequestLoggerMiddleware);

		// Mount Better Auth handler at /api/auth/*
		// https://www.better-auth.com/docs/integrations/express
		this.authInstance = this.authService.getAuthInstance();
		const authHandler = toNodeHandler(this.authInstance as unknown as Parameters<typeof toNodeHandler>[0]);
		this.app.all("/api/auth/*splat", (req, res) => {
			console.log("[BetterAuth] Request received:", req.method, req.url);
			return authHandler(req, res);
		});

		this.app.use(express.json());
		this.app.use(express.urlencoded({ extended: true }));
	}

My better auth config:

betterAuth({
			database:
				dbProvider === DatabaseProvider.DRIZZLE
					? drizzleAdapter(databaseClient, {
							provider: drizzleProvider
						})
					: // Future: Add adapters for Prisma, TypeORM, etc.
						drizzleAdapter(databaseClient, {
							provider: drizzleProvider
						}),
			secret: this.config.auth.secret,
			baseURL: this.config.auth.baseUrl,
			session: {
				// HTTP-only cookies for security
				cookieCache: {
					enabled: true,
					maxAge: 60 * 60 // 1 hour
				},
				// Session expiration
				expiresIn: 60 * 60 * 24 * 7, // 7 days
				updateAge: 60 * 60 * 24 // Update session every 24 hours
			},
			emailAndPassword: {
				enabled: true,
				requireEmailVerification: false,
				minPasswordLength: 8,
				maxPasswordLength: 128,
				sendResetPassword: async ({ user, token }, _request) => {
					// Generate frontend reset password URL with token
					const frontendResetUrl = `${this.config.urls.frontend}/auth/reset-password?token=${token}`;
					const html = getPasswordResetEmailTemplate(user.email, frontendResetUrl);
					await this.emailService.sendEmail(user.email, "Reset Your Password", html);
				},
				onPasswordReset: async ({ user }, _request) => {
					console.log(`Password for user ${user.email} has been reset.`);
				}
			},
			// emailVerification: {
			// 	sendOnSignUp: false,
			// 	sendVerificationEmail: async ({ user, url }) => {
			// 		const html = getVerificationEmailTemplate(user.email, url);
			// 		await this.emailService.sendEmail(user.email, "Verify Your Email Address", html);
			// 	}
			// },
			trustedOrigins: [this.config.urls.frontend, this.config.urls.backend],
			hooks: {},
			plugins: this.buildPlugins(paymentProvider, paymentClient)
		});

Make the request programatically (this hangs & the test times out, same happens manually in Swagger):

await authService.getAuthInstance().api.signUpEmail({
				body: {
					name: "John Doe", // required
					email: "john.doe@example.com", // required
					password: "password1234", // required
					image: "https://example.com/image.png",
					callbackURL: "https://example.com/callback",
				},
			});

Current vs. Expected behavior

I'm using the Tsed.dev framework, which uses Express under the hood. I'm on the latest version, meaning v5 is being used.

I've realised that if I try to hit the /api/auth/sign-up/email route via swagger or using programatically SuperTest (I'm trying to write integration tests, using a Postgres database running with TestContainers), the request just hangs & times out. I have also tried to implement it programatically using the server auth client & get the same problem as it's doing the same thing under the hood.

Whereas, when I hit the same endpoint, using the "better-auth/react" on my frontend (Tanstack Start) via the browser, I get a 200 response with the cookies returning.

I have ensured that I have followed guidance on similar issues to ensure that express middlewares come after setting up better auth:

		const authHandler = toNodeHandler(this.authInstance as unknown as Parameters<typeof toNodeHandler>[0]);
		this.app.all("/api/auth/*splat", (req, res) => {
			console.log("[BetterAuth] Request received:", req.method, req.url);
			return authHandler(req, res);
		});

		this.app.use(express.json());
		this.app.use(express.urlencoded({ extended: true }));

So, at this point, I'm baffled what regarding what more I have to do, to be able to get a response from this API endpoint to be able to create a new user in the setup phase of my test. Simultaneously, it would also be useful if I could also create a user via Swagger.

At a guess, it might be because better-auth is designed to be used in a browser based client framework such as Next or Tanstack to be able to consume the cookies whereas better-auth doesn't return a response end as would be expected by an API framework such as express.... however I feel like I must be missing something obvious & that there must be a solution for this?

Thanks

What version of Better Auth are you using?

1.4.6

System info

Mac OSX Sequoia 15.5
I don't have the CLI installed.

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

No response

Originally created by @karam94 on GitHub (Dec 29, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce Framework: [Tsed.dev](https://tsed.dev/) using Express v5 Server.ts (what's relevant): ``` public $beforeRoutesInit(): void { console.log("ENTERED SERVER"); this.app.use( cors({ origin: this.config.urls.frontend, credentials: true }) ); this.app.use(RequestLoggerMiddleware); // Mount Better Auth handler at /api/auth/* // https://www.better-auth.com/docs/integrations/express this.authInstance = this.authService.getAuthInstance(); const authHandler = toNodeHandler(this.authInstance as unknown as Parameters<typeof toNodeHandler>[0]); this.app.all("/api/auth/*splat", (req, res) => { console.log("[BetterAuth] Request received:", req.method, req.url); return authHandler(req, res); }); this.app.use(express.json()); this.app.use(express.urlencoded({ extended: true })); } ``` My better auth config: ``` betterAuth({ database: dbProvider === DatabaseProvider.DRIZZLE ? drizzleAdapter(databaseClient, { provider: drizzleProvider }) : // Future: Add adapters for Prisma, TypeORM, etc. drizzleAdapter(databaseClient, { provider: drizzleProvider }), secret: this.config.auth.secret, baseURL: this.config.auth.baseUrl, session: { // HTTP-only cookies for security cookieCache: { enabled: true, maxAge: 60 * 60 // 1 hour }, // Session expiration expiresIn: 60 * 60 * 24 * 7, // 7 days updateAge: 60 * 60 * 24 // Update session every 24 hours }, emailAndPassword: { enabled: true, requireEmailVerification: false, minPasswordLength: 8, maxPasswordLength: 128, sendResetPassword: async ({ user, token }, _request) => { // Generate frontend reset password URL with token const frontendResetUrl = `${this.config.urls.frontend}/auth/reset-password?token=${token}`; const html = getPasswordResetEmailTemplate(user.email, frontendResetUrl); await this.emailService.sendEmail(user.email, "Reset Your Password", html); }, onPasswordReset: async ({ user }, _request) => { console.log(`Password for user ${user.email} has been reset.`); } }, // emailVerification: { // sendOnSignUp: false, // sendVerificationEmail: async ({ user, url }) => { // const html = getVerificationEmailTemplate(user.email, url); // await this.emailService.sendEmail(user.email, "Verify Your Email Address", html); // } // }, trustedOrigins: [this.config.urls.frontend, this.config.urls.backend], hooks: {}, plugins: this.buildPlugins(paymentProvider, paymentClient) }); ``` Make the request programatically (this hangs & the test times out, same happens manually in Swagger): ``` await authService.getAuthInstance().api.signUpEmail({ body: { name: "John Doe", // required email: "john.doe@example.com", // required password: "password1234", // required image: "https://example.com/image.png", callbackURL: "https://example.com/callback", }, }); ``` ### Current vs. Expected behavior I'm using the [Tsed.dev](https://tsed.dev/) framework, which uses Express under the hood. I'm on the latest version, meaning v5 is being used. I've realised that if I try to hit the `/api/auth/sign-up/email` route via swagger or using programatically SuperTest (I'm trying to write integration tests, using a Postgres database running with TestContainers), the request just hangs & times out. I have also tried to implement it programatically using the [server auth client](https://www.better-auth.com/docs/authentication/email-password#api-method-sign-up-email) & get the same problem as it's doing the same thing under the hood. Whereas, when I hit the same endpoint, using the `"better-auth/react"` on my frontend (Tanstack Start) via the browser, I get a 200 response with the cookies returning. I have ensured that I have followed guidance on similar issues to ensure that express middlewares come after setting up better auth: ``` const authHandler = toNodeHandler(this.authInstance as unknown as Parameters<typeof toNodeHandler>[0]); this.app.all("/api/auth/*splat", (req, res) => { console.log("[BetterAuth] Request received:", req.method, req.url); return authHandler(req, res); }); this.app.use(express.json()); this.app.use(express.urlencoded({ extended: true })); ``` So, at this point, I'm baffled what regarding what more I have to do, to be able to get a response from this API endpoint to be able to create a new user in the setup phase of my test. Simultaneously, it would also be useful if I could also create a user via Swagger. At a guess, it might be because better-auth is designed to be used in a browser based client framework such as Next or Tanstack to be able to consume the cookies whereas better-auth doesn't return a response end as would be expected by an API framework such as express.... however I feel like I must be missing something obvious & that there must be a solution for this? Thanks ### What version of Better Auth are you using? 1.4.6 ### System info ```bash Mac OSX Sequoia 15.5 I don't have the CLI installed. ``` ### 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 _No response_
GiteaMirror added the express label 2026-03-13 10:09:14 -05:00
Author
Owner

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

It seems like you're dealing with sign-up requests hanging and timing out when using the Better Auth framework with your Express app. This is a known issue, and there may be a few things to check or troubleshoot.

  1. Middlewares Order: Ensure that the Better Auth handler is mounted correctly on your route before any other middlewares that could affect the request lifecycle. Based on what you provided, moving this.authInstance = this.authService.getAuthInstance(); and the related handler setup before setting other middlewares may help avoid interference.

  2. CORS Configuration: Since you're using CORS, verify that it’s correctly set up to accept requests from your frontend origin, and make sure credentials are being sent properly. If there are discrepancies in your configurations, it could lead to requests being stalled.

  3. Session Management: If Better Auth is designed mainly for browser-based clients, it might not respond as expected for API requests similar to those made directly in a browser. Investigate if there are specific configurations or plugins needed to handle server-side sessions properly.

  4. Debugging: Use console logging effectively by logging the requests that reach your auth handler to see if they're being processed correctly. This can help isolate where the breakdown is occurring.

  5. Testing Tools: You mentioned using tools like Swagger and SuperTest. Make sure that these tools are respecting the server's CORS settings, as cross-origin requests can behave differently in such test environments.

For further assistance, consider reaching out to the Better Auth team by tagging @bekacru or @ping__ who can provide insights on this issue.

What specific part of your setup do you feel might need adjustment, or is there a different aspect you'd like to explore further?

If you need more help, tag @better-auth-agent in a comment so I can respond.

DiagramDiscordGitHub

Diagram Join Star

@better-auth-agent[bot] commented on GitHub (Dec 29, 2025): It seems like you're dealing with sign-up requests hanging and timing out when using the Better Auth framework with your Express app. This is a known issue, and there may be a few things to check or troubleshoot. 1. **Middlewares Order**: Ensure that the Better Auth handler is mounted correctly on your route before any other middlewares that could affect the request lifecycle. Based on what you provided, moving `this.authInstance = this.authService.getAuthInstance();` and the related handler setup before setting other middlewares may help avoid interference. 2. **CORS Configuration**: Since you're using CORS, verify that it’s correctly set up to accept requests from your frontend origin, and make sure credentials are being sent properly. If there are discrepancies in your configurations, it could lead to requests being stalled. 3. **Session Management**: If Better Auth is designed mainly for browser-based clients, it might not respond as expected for API requests similar to those made directly in a browser. Investigate if there are specific configurations or plugins needed to handle server-side sessions properly. 4. **Debugging**: Use console logging effectively by logging the requests that reach your auth handler to see if they're being processed correctly. This can help isolate where the breakdown is occurring. 5. **Testing Tools**: You mentioned using tools like Swagger and SuperTest. Make sure that these tools are respecting the server's CORS settings, as cross-origin requests can behave differently in such test environments. For further assistance, consider reaching out to the Better Auth team by tagging @bekacru or @ping__ who can provide insights on this issue. What specific part of your setup do you feel might need adjustment, or is there a different aspect you'd like to explore further? _If you need more help, tag @better-auth-agent in a comment so I can respond._ <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) • [Discord](https://discord.gg/fG2XXEuQX3) • [GitHub](https://github.com/Skyvern-AI/Skyvern) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/fG2XXEuQX3) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/Skyvern-AI/Skyvern)
Author
Owner

@dosubot[bot] commented on GitHub (Dec 29, 2025):

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

Your sign-up requests are hanging because of a middleware order issue and a strict content type requirement in Better Auth 1.4.6.

1. Middleware order:
The Better Auth handler must be mounted before any body parsing middleware like express.json() or express.urlencoded(). If you mount these before Better Auth, they consume the request body, so Better Auth can't read it, causing the request to hang indefinitely. In your code, you have:

this.app.all("/api/auth/*splat", (req, res) => { ... });
this.app.use(express.json());
this.app.use(express.urlencoded({ extended: true }));

This is correct. However, if any other middleware (including Ts.ED internals or global body parsers) runs before the Better Auth handler, you can still hit this problem. Double-check your Ts.ED config and make sure no global body parser is registered before $beforeRoutesInit runs. If in doubt, try moving the Better Auth handler to the very top, before any other .use() or .all() calls, and see if the issue resolves [source] [source].

2. Content-Type requirement:
The /api/auth/sign-up/email endpoint only accepts Content-Type: application/json. If you send application/x-www-form-urlencoded (which Swagger and some HTTP clients do by default), the request will hang or return a 415 error [source]. Always set the header and send a JSON body:

{
  "name": "John Doe",
  "email": "john.doe@example.com",
  "password": "password1234"
}

with Content-Type: application/json.

3. Route pattern:
For Express v5, the recommended pattern is app.all('/api/auth/*splat', ...) or app.all('/api/auth/{*any}', ...), but the key is that the route must match your baseURL exactly. If you use a subrouter or proxy, make sure the path isn't being stripped or altered [source].

4. Programmatic user creation for tests:
You can call auth.api.signUpEmail() directly in your test setup, as long as you pass a JSON body and the correct headers. Alternatively, for seeding users, you can use the internal adapter API to create users directly, bypassing the HTTP endpoint [source].

5. Troubleshooting checklist:

  • Move the Better Auth handler to the very top of your middleware stack.
  • Ensure no global body parser is registered before Better Auth.
  • Confirm your requests use Content-Type: application/json.
  • Double-check your route pattern and baseURL alignment.
  • If you have custom hooks/plugins, make sure they always resolve.

If you follow these steps, your sign-up endpoint should respond correctly to both programmatic and Swagger requests. The endpoint is not browser-only; it works for API clients as long as the above requirements are met [source].

Let me know if you want a minimal working Express v5 + Better Auth example.

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

@dosubot[bot] commented on GitHub (Dec 29, 2025): <!-- Greeting --> Hi @karam94! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> Your sign-up requests are hanging because of a middleware order issue and a strict content type requirement in Better Auth 1.4.6. **1. Middleware order:** The Better Auth handler must be mounted before any body parsing middleware like `express.json()` or `express.urlencoded()`. If you mount these before Better Auth, they consume the request body, so Better Auth can't read it, causing the request to hang indefinitely. In your code, you have: ```js this.app.all("/api/auth/*splat", (req, res) => { ... }); this.app.use(express.json()); this.app.use(express.urlencoded({ extended: true })); ``` This is correct. However, if *any* other middleware (including Ts.ED internals or global body parsers) runs before the Better Auth handler, you can still hit this problem. Double-check your Ts.ED config and make sure no global body parser is registered before `$beforeRoutesInit` runs. If in doubt, try moving the Better Auth handler to the very top, before any other `.use()` or `.all()` calls, and see if the issue resolves [[source]](https://github.com/better-auth/better-auth/issues/2975) [[source]](https://github.com/better-auth/better-auth/issues/3295). **2. Content-Type requirement:** The `/api/auth/sign-up/email` endpoint only accepts `Content-Type: application/json`. If you send `application/x-www-form-urlencoded` (which Swagger and some HTTP clients do by default), the request will hang or return a 415 error [[source]](https://github.com/better-auth/better-auth/issues/6195). Always set the header and send a JSON body: ```json { "name": "John Doe", "email": "john.doe@example.com", "password": "password1234" } ``` with `Content-Type: application/json`. **3. Route pattern:** For Express v5, the recommended pattern is `app.all('/api/auth/*splat', ...)` or `app.all('/api/auth/{*any}', ...)`, but the key is that the route must match your `baseURL` exactly. If you use a subrouter or proxy, make sure the path isn't being stripped or altered [[source]](https://github.com/better-auth/better-auth/issues/6636). **4. Programmatic user creation for tests:** You can call `auth.api.signUpEmail()` directly in your test setup, as long as you pass a JSON body and the correct headers. Alternatively, for seeding users, you can use the internal adapter API to create users directly, bypassing the HTTP endpoint [[source]](https://github.com/better-auth/better-auth/issues/3323). **5. Troubleshooting checklist:** - Move the Better Auth handler to the very top of your middleware stack. - Ensure no global body parser is registered before Better Auth. - Confirm your requests use `Content-Type: application/json`. - Double-check your route pattern and `baseURL` alignment. - If you have custom hooks/plugins, make sure they always resolve. If you follow these steps, your sign-up endpoint should respond correctly to both programmatic and Swagger requests. The endpoint is not browser-only; it works for API clients as long as the above requirements are met [[source]](https://github.com/better-auth/better-auth/issues/6195). Let me know if you want a minimal working Express v5 + Better Auth example. <!-- 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/9abc860c-d2ea-4674-b081-bf04f1c4539a?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/9abc860c-d2ea-4674-b081-bf04f1c4539a?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/9abc860c-d2ea-4674-b081-bf04f1c4539a?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/9abc860c-d2ea-4674-b081-bf04f1c4539a?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/9abc860c-d2ea-4674-b081-bf04f1c4539a?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/9abc860c-d2ea-4674-b081-bf04f1c4539a?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/9abc860c-d2ea-4674-b081-bf04f1c4539a?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=Your%20sign-up%20requests%20are%20hanging%20because%20of%20a%20middleware%20order%20issue%20and%20a%20strict%20content%20type%20requirement%20in%20Better%20Auth%201.4.6.%0A%0A%2A%2A1.%20Middleware%20order%3A%2A%2A%20%20%0AThe%20Better%20Auth%20handler%20must%20be%20mounted%20before%20any%20body%20parsing%20middleware%20like%20%60express.json%28%29%60%20or%20%60express.urlencoded%28%29%60.%20If%20you%20mount%20these%20before%20Better%20Auth%2C%20they%20consume%20the%20request%20body%2C%20so%20Better%20Auth%20can%27t%20read%20it%2C%20causing%20the%20request%20to%20hang%20indefinitely.%20In%20your%20code%2C%20you%20have%3A%0A%0A%60%60%60js%0Athis.app.all%28%22/api/auth/%2Asplat%22%2C%20%28req%2C%20res%29%20%3D%3E%20%7B%20...%20%7D%29%3B%0Athis.app.use%28express.json%28%29%29%3B%0Athis.app.use%28express.urlencoded%28%7B%20extended%3A%20true%20%7D%29%29%3B%0A%60%60%60%0AThis%20is%20correct.%20However%2C%20if%20%2Aany%2A%20other%20middleware%20%28including%20Ts.ED%20internals%20or%20global%20body%20parsers%29%20runs%20before%20the%20Better%20Auth%20handler%2C%20you%20can%20still%20hit%20this%20problem.%20Double-check%20your%20Ts.ED%20config%20and%20make%20sure%20no%20global%20body%20parser%20is%20registered%20before%20%60%24beforeRoutesInit%60%20runs.%20If%20in%20doubt%2C%20try%20moving%20the%20Better%20Auth%20handler%20to%20the%20very%20top%2C%20before%20any%20other%20%60.use%28%29%60%20or%20%60.all%28%29%60%20calls%2C%20and%20see%20if%20the%20issue%20resolves%20%5B%5Bsource%5D%5D%28https%3A//github.com/better-auth/better-auth/issues/2975%29%20%5B%5Bsource%5D%5D%28https%3A//github.com/better-auth/better-auth/issues/3295%29.%0A%0A%2A%2A2.%20Content-Type%20requirement%3A%2A%2A%20%20%0AThe%20%60/api/auth/sign-up/email%60%20endpoint%20only%20accepts%20%60Content-Type%3A%20application/json%60.%20If%20you%20send%20%60application/x-www-form-urlencoded%60%20%28which%20Swagger%20and%20some%20HTTP%20clients%20do%20by%20default%29%2C%20the%20request%20will%20hang%20or%20return%20a%20415%20error%20%5B%5Bsource%5D%5D%28https%3A//github.com/better-auth/better-auth/issues/6195%29.%20Always%20set%20the%20header%20and%20send%20a%20JSON%20body%3A%0A%60%60%60json%0A%7B%0A%20%20%22name%22%3A%20%22John%20Doe%22%2C%0A%20%20%22email%22%3A%20%22john.doe%40example.com%22%2C%0A%20%20%22password%22%3A%20%22password1234%22%0A%7D%0A%60%60%60%0Awith%20%60Content-Type%3A%20application/json%60.%0A%0A%2A%2A3.%20Route%20pattern%3A%2A%2A%20%20%0AFor%20Express%20v5%2C%20the%20recommended%20pattern%20is%20%60app.all%28%27/api/auth/%2Asplat%27%2C%20...%29%60%20or%20%60app.all%28%27/api/auth/%7B%2Aany%7D%27%2C%20...%29%60%2C%20but%20the%20key%20is%20that%20the%20route%20must%20match%20your%20%60baseURL%60%20exactly.%20If%20you%20use%20a%20subrouter%20or%20proxy%2C%20make%20sure%20the%20path%20isn%27t%20being%20stripped%20or%20altered%20%5B%5Bsource%5D%5D%28https%3A//github.com/better-auth/better-auth/issues/6636%29.%0A%0A%2A%2A4.%20Programmatic%20user%20creation%20for%20tests%3A%2A%2A%20%20%0AYou%20can%20call%20%60auth.api.signUpEmail%28%29%60%20directly%20in%20your%20test%20setup%2C%20as%20long%20as%20you%20pass%20a%20JSON%20body%20and%20the%20correct%20headers.%20Alternatively%2C%20for%20seeding%20users%2C%20you%20can%20use%20the%20internal%20adapter%20API%20to%20create%20users%20directly%2C%20bypassing%20the%20HTTP%20endpoint%20%5B%5Bsource%5D%5D%28https%3A//github.com/better-auth/better-auth/issues/3323%29.%0A%0A%2A%2A5.%20Troubleshooting%20checklist%3A%2A%2A%0A-%20Move%20the%20Better%20Auth%20handler%20to%20the%20very%20top%20of%20your%20middleware%20stack.%0A-%20Ensure%20no%20global%20body%20parser%20is%20registered%20before%20Better%20Auth.%0A-%20Confirm%20your%20requests%20use%20%60Content-Type%3A%20application/json%60.%0A-%20Double-check%20your%20route%20pattern%20and%20%60baseURL%60%20alignment.%0A-%20If%20you%20have%20custom%20hooks/plugins%2C%20make%20sure%20they%20always%20resolve.%0A%0AIf%20you%20follow%20these%20steps%2C%20your%20sign-up%20endpoint%20should%20respond%20correctly%20to%20both%20programmatic%20and%20Swagger%20requests.%20The%20endpoint%20is%20not%20browser-only%3B%20it%20works%20for%20API%20clients%20as%20long%20as%20the%20above%20requirements%20are%20met%20%5B%5Bsource%5D%5D%28https%3A//github.com/better-auth/better-auth/issues/6195%29.%0A%0ALet%20me%20know%20if%20you%20want%20a%20minimal%20working%20Express%20v5%20%2B%20Better%20Auth%20example.)&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/7044)
Author
Owner

@karam94 commented on GitHub (Dec 29, 2025):

@dosu You said:

Confirm your requests use Content-Type: application/json.

So, I went to double check that Swagger was doing so.
It showed the following cURL request:

curl -X 'POST' \
  'http://localhost:4001/api/auth/sign-up/email' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "test",
  "email": "test@test.com",
  "password": "Password123!"
}'

Swagger continued spinning/hanging/timing out.
Whereas the same cURL request in my terminal, immediately returned a response of:
{"code":"USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL","message":"User already exists. Use another email."}

This further suggests we are seeing varied behaviour.
I do not think it is down to the Content-Type because both Swagger & my terminal will have made the same cURL request.
Does this information give you any hints that might be relevant? Why are we seeing different better-auth behaviour?

@karam94 commented on GitHub (Dec 29, 2025): @dosu You said: > Confirm your requests use Content-Type: application/json. So, I went to double check that Swagger was doing so. It showed the following cURL request: ``` curl -X 'POST' \ 'http://localhost:4001/api/auth/sign-up/email' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -d '{ "name": "test", "email": "test@test.com", "password": "Password123!" }' ``` Swagger continued spinning/hanging/timing out. Whereas the same cURL request in my terminal, immediately returned a response of: `{"code":"USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL","message":"User already exists. Use another email."}` This further suggests we are seeing varied behaviour. I do not think it is down to the Content-Type because both Swagger & my terminal will have made the same cURL request. Does this information give you any hints that might be relevant? Why are we seeing different better-auth behaviour?
Author
Owner

@dosubot[bot] commented on GitHub (Dec 29, 2025):

This is almost certainly due to Better Auth's originCheckMiddleware, which enforces strict origin validation for POST requests that include cookies. Swagger UI (running in the browser) sends both cookies and an Origin header, so Better Auth checks if the Origin matches your trustedOrigins list. If it doesn't, or if the Origin header is missing, the middleware throws a FORBIDDEN error—which can cause Swagger to hang or time out if the error isn't surfaced to the UI. In contrast, cURL requests typically don't send cookies or an Origin header, so they bypass this check and get a normal response (source).

To confirm, check your server logs for FORBIDDEN or origin-related errors when Swagger requests hang. You can also temporarily set advanced: { disableOriginCheck: true } in your Better Auth config (for development only) to see if Swagger starts working. If it does, add the exact Swagger UI origin (including protocol, hostname, and port) to your trustedOrigins array and restart your backend. Make sure there are no duplicate/conflicting CORS middlewares on your auth endpoints (source).

If you want to debug further, inspect the actual request headers sent by Swagger UI (especially Origin and Cookie) and compare them to cURL. The mismatch in origin validation is the root cause of the different behaviors.

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

@dosubot[bot] commented on GitHub (Dec 29, 2025): <!-- Answer --> This is almost certainly due to Better Auth's originCheckMiddleware, which enforces strict origin validation for POST requests that include cookies. Swagger UI (running in the browser) sends both cookies and an Origin header, so Better Auth checks if the Origin matches your trustedOrigins list. If it doesn't, or if the Origin header is missing, the middleware throws a FORBIDDEN error—which can cause Swagger to hang or time out if the error isn't surfaced to the UI. In contrast, cURL requests typically don't send cookies or an Origin header, so they bypass this check and get a normal response ([source](https://github.com/better-auth/better-auth/issues/4052#issuecomment-3196279723)). To confirm, check your server logs for FORBIDDEN or origin-related errors when Swagger requests hang. You can also temporarily set `advanced: { disableOriginCheck: true }` in your Better Auth config (for development only) to see if Swagger starts working. If it does, add the exact Swagger UI origin (including protocol, hostname, and port) to your trustedOrigins array and restart your backend. Make sure there are no duplicate/conflicting CORS middlewares on your auth endpoints ([source](https://github.com/better-auth/better-auth/issues/3362#issuecomment-3067202014)). If you want to debug further, inspect the actual request headers sent by Swagger UI (especially Origin and Cookie) and compare them to cURL. The mismatch in origin validation is the root cause of the different behaviors. <!-- 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/b258b16f-b409-49c8-9639-ef82633cc20c?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/b258b16f-b409-49c8-9639-ef82633cc20c?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/b258b16f-b409-49c8-9639-ef82633cc20c?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/b258b16f-b409-49c8-9639-ef82633cc20c?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/b258b16f-b409-49c8-9639-ef82633cc20c?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/b258b16f-b409-49c8-9639-ef82633cc20c?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/b258b16f-b409-49c8-9639-ef82633cc20c?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=This%20is%20almost%20certainly%20due%20to%20Better%20Auth%27s%20originCheckMiddleware%2C%20which%20enforces%20strict%20origin%20validation%20for%20POST%20requests%20that%20include%20cookies.%20Swagger%20UI%20%28running%20in%20the%20browser%29%20sends%20both%20cookies%20and%20an%20Origin%20header%2C%20so%20Better%20Auth%20checks%20if%20the%20Origin%20matches%20your%20trustedOrigins%20list.%20If%20it%20doesn%27t%2C%20or%20if%20the%20Origin%20header%20is%20missing%2C%20the%20middleware%20throws%20a%20FORBIDDEN%20error%E2%80%94which%20can%20cause%20Swagger%20to%20hang%20or%20time%20out%20if%20the%20error%20isn%27t%20surfaced%20to%20the%20UI.%20In%20contrast%2C%20cURL%20requests%20typically%20don%27t%20send%20cookies%20or%20an%20Origin%20header%2C%20so%20they%20bypass%20this%20check%20and%20get%20a%20normal%20response%C2%A0%28%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/issues/4052%23issuecomment-3196279723%29%29.%0A%0ATo%20confirm%2C%20check%20your%20server%20logs%20for%20FORBIDDEN%20or%20origin-related%20errors%20when%20Swagger%20requests%20hang.%20You%20can%20also%20temporarily%20set%20%60advanced%3A%20%7B%20disableOriginCheck%3A%20true%20%7D%60%20in%20your%20Better%20Auth%20config%20%28for%20development%20only%29%20to%20see%20if%20Swagger%20starts%20working.%20If%20it%20does%2C%20add%20the%20exact%20Swagger%20UI%20origin%20%28including%20protocol%2C%20hostname%2C%20and%20port%29%20to%20your%20trustedOrigins%20array%20and%20restart%20your%20backend.%20Make%20sure%20there%20are%20no%20duplicate/conflicting%20CORS%20middlewares%20on%20your%20auth%20endpoints%C2%A0%28%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/issues/3362%23issuecomment-3067202014%29%29.%0A%0AIf%20you%20want%20to%20debug%20further%2C%20inspect%20the%20actual%20request%20headers%20sent%20by%20Swagger%20UI%20%28especially%20Origin%20and%20Cookie%29%20and%20compare%20them%20to%20cURL.%20The%20mismatch%20in%20origin%20validation%20is%20the%20root%20cause%20of%20the%20different%20behaviors.)&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/7044)
Author
Owner

@karam94 commented on GitHub (Dec 29, 2025):

advanced: { disableOriginCheck: true }

@dosu Nope, it won't be this.
The initial request hangs, however if I clear it in Swagger & re-execute, I then get a valid 422 response (because I'm trying to recreate the user that was just created, meaning the initial request worked but we just don't receive a response back.
If the issue was an origin check, then we wouldn't get a response the second time round.

@karam94 commented on GitHub (Dec 29, 2025): > advanced: { disableOriginCheck: true } @dosu Nope, it won't be this. The initial request hangs, however if I clear it in Swagger & re-execute, I then get a valid 422 response (because I'm trying to recreate the user that was just created, meaning the initial request worked but we just don't receive a response back. If the issue was an origin check, then we wouldn't get a response the second time round.
Author
Owner

@bytaesu commented on GitHub (Dec 29, 2025):

Hi @karam94,

I’m going to look into this, do you have a minimal public repo where this issue can be reproduced? 🙂

@bytaesu commented on GitHub (Dec 29, 2025): Hi @karam94, I’m going to look into this, do you have a minimal public repo where this issue can be reproduced? 🙂
Author
Owner

@karam94 commented on GitHub (Dec 31, 2025):

Hi @karam94,

I’m going to look into this, do you have a minimal public repo where this issue can be reproduced? 🙂

Hello!
Thanks for the swift response.
Fortunately, I had a debugging session this morning & realised that by setting createCustomerOnSignUp: false for stripe, this problem gets solved. It seems that when I run the test/manually try to sign a user up initially, Stripe was hanging (even though it was being mocked out).

So to conclude, this isn't a better-auth issue (sorry about that, but I was baffled for a while... the joys of little sleep thanks to a 1 year old 😄) - however I would encourage you guys to look towards shipping better-auth with a test utils library or something similar to help guide people on best practices for writing integration tests with it.

Thanks!

@karam94 commented on GitHub (Dec 31, 2025): > Hi [@karam94](https://github.com/karam94), > > I’m going to look into this, do you have a minimal public repo where this issue can be reproduced? 🙂 Hello! Thanks for the swift response. Fortunately, I had a debugging session this morning & realised that by setting `createCustomerOnSignUp: false` for stripe, this problem gets solved. It seems that when I run the test/manually try to sign a user up initially, Stripe was hanging (even though it was being mocked out). So to conclude, this isn't a better-auth issue (sorry about that, but I was baffled for a while... the joys of little sleep thanks to a 1 year old 😄) - however I would encourage you guys to look towards shipping better-auth with a test utils library or something similar to help guide people on best practices for writing integration tests with it. Thanks!
Author
Owner

@bytaesu commented on GitHub (Dec 31, 2025):

Hello @karam94,

We're planning to restructure our docs and hope this will provide a better experience 😁

@bytaesu commented on GitHub (Dec 31, 2025): Hello @karam94, We're planning to restructure our docs and hope this will provide a better experience 😁
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#2627