CORS headers not preserved when using toNextJsHandler #1820

Closed
opened 2026-03-13 09:05:34 -05:00 by GiteaMirror · 5 comments
Owner

Originally created by @jan-arnez on GitHub (Sep 1, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. npx create-next-app@latest --api
  2. Set up better-auth with Next.js using toNextJsHandler
  3. Create auth config and define "trustedOrigins"
  4. Create new Next.js application and define authClient with baseURL pointing to Next.js backend
  5. Make request from frontend to Next.js backend

Current vs. Expected behavior

Expected Behavior:
CORS headers should be preserved in the response from better-auth endpoints, allowing cross-origin requests to succeed.
Actual Behavior:
CORS headers are stripped or not applied to the response, causing cross-origin requests to fail.

What version of Better Auth are you using?

1.3.7

System info

System:
    OS: Windows 11 10.0.26100
    CPU: (16) x64 AMD Ryzen 7 7745HX with Radeon Graphics        
    Memory: 16.23 GB / 31.19 GB
  Browsers:
    Edge: Chromium (129.0.2792.89)
    Internet Explorer: 11.0.26100.1882

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

Backend, Client

Auth config (if applicable)

export const auth = betterAuth({
  trustedOrigins: [
    "http://localhost:3000",
    "http://localhost:3001",
    "http://localhost:5000",
  ],

  database: prismaAdapter(prisma, {
    provider: "postgresql",
  }),

  emailAndPassword: {
    enabled: true,
    requireEmailVerification: false,
  },

  plugins: [organization(), admin(), nextCookies()],
});

Additional context

auth-client.tsx

export const authClient = createAuthClient({
  baseURL: "http://localhost:5000",
});

Current solution is using middleware but would preffer trustedOrigin in config to do this instead.

middleware.tsx

import { NextRequest, NextResponse } from "next/server";

export const allowedOrigins = [
  "http://localhost:3000",
  "http://localhost:3001",
"http://localhost:5000",
];

export function middleware(request: NextRequest) {
  const origin = request.headers.get("origin");

  // Handle CORS preflight requests
  if (request.method === "OPTIONS") {
    const response = new NextResponse(null, { status: 200 });

    if (origin && allowedOrigins.includes(origin)) {
      response.headers.set("Access-Control-Allow-Origin", origin);
    }

    response.headers.set(
      "Access-Control-Allow-Methods",
      "GET, POST, PUT, DELETE, OPTIONS"
    );
    response.headers.set(
      "Access-Control-Allow-Headers",
      "Content-Type, Authorization, X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Date, X-Api-Version"
    );
    response.headers.set("Access-Control-Allow-Credentials", "true");

    return response;
  }

  // Handle actual requests
  const response = NextResponse.next();

  if (origin && allowedOrigins.includes(origin)) {
    response.headers.set("Access-Control-Allow-Origin", origin);
  }

  response.headers.set("Access-Control-Allow-Credentials", "true");

  return response;
}

export const config = {
  matcher: "/api/auth/:path*",
};

Originally created by @jan-arnez on GitHub (Sep 1, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. npx create-next-app@latest --api 2. Set up better-auth with Next.js using toNextJsHandler 3. Create auth config and define "trustedOrigins" 4. Create new Next.js application and define authClient with baseURL pointing to Next.js backend 5. Make request from frontend to Next.js backend ### Current vs. Expected behavior Expected Behavior: CORS headers should be preserved in the response from better-auth endpoints, allowing cross-origin requests to succeed. Actual Behavior: CORS headers are stripped or not applied to the response, causing cross-origin requests to fail. ### What version of Better Auth are you using? 1.3.7 ### System info ```bash System: OS: Windows 11 10.0.26100 CPU: (16) x64 AMD Ryzen 7 7745HX with Radeon Graphics Memory: 16.23 GB / 31.19 GB Browsers: Edge: Chromium (129.0.2792.89) Internet Explorer: 11.0.26100.1882 ``` ### Which area(s) are affected? (Select all that apply) Backend, Client ### Auth config (if applicable) ```typescript export const auth = betterAuth({ trustedOrigins: [ "http://localhost:3000", "http://localhost:3001", "http://localhost:5000", ], database: prismaAdapter(prisma, { provider: "postgresql", }), emailAndPassword: { enabled: true, requireEmailVerification: false, }, plugins: [organization(), admin(), nextCookies()], }); ``` ### Additional context **auth-client.tsx** ```typescript export const authClient = createAuthClient({ baseURL: "http://localhost:5000", }); ``` Current solution is using middleware but would preffer trustedOrigin in config to do this instead. **middleware.tsx** ```typescript import { NextRequest, NextResponse } from "next/server"; export const allowedOrigins = [ "http://localhost:3000", "http://localhost:3001", "http://localhost:5000", ]; export function middleware(request: NextRequest) { const origin = request.headers.get("origin"); // Handle CORS preflight requests if (request.method === "OPTIONS") { const response = new NextResponse(null, { status: 200 }); if (origin && allowedOrigins.includes(origin)) { response.headers.set("Access-Control-Allow-Origin", origin); } response.headers.set( "Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS" ); response.headers.set( "Access-Control-Allow-Headers", "Content-Type, Authorization, X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Date, X-Api-Version" ); response.headers.set("Access-Control-Allow-Credentials", "true"); return response; } // Handle actual requests const response = NextResponse.next(); if (origin && allowedOrigins.includes(origin)) { response.headers.set("Access-Control-Allow-Origin", origin); } response.headers.set("Access-Control-Allow-Credentials", "true"); return response; } export const config = { matcher: "/api/auth/:path*", }; ```
GiteaMirror added the nextjs label 2026-03-13 09:05:34 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Sep 1, 2025):

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

This is a known limitation in Better Auth v1.3.7: setting trustedOrigins in your config does not automatically add CORS headers to responses when using toNextJsHandler. As a result, cross-origin requests will fail unless you manually handle CORS in your Next.js middleware or API routes. This is why your current middleware approach works, and why it's still required even with trustedOrigins configured.

The recommended workaround is to keep your explicit CORS middleware for the /api/auth routes, where you define allowed origins and set the necessary CORS headers (Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Credentials) for both preflight (OPTIONS) and actual requests. This approach is confirmed by multiple users and maintainers as the current best practice for Next.js setups with Better Auth see details and example.

trustedOrigins is used for internal security checks, but does not handle CORS headers automatically in Next.js. If you want this to be handled by Better Auth itself, you would need to watch for future updates or contribute to the project.

For cross-origin authentication to work smoothly, also ensure your cookie settings include sameSite: 'none' and secure: true, and that all frontend origins are listed in trustedOrigins reference.

In summary: keep your CORS middleware as you have it now—trustedOrigins alone is not enough for CORS header handling in the current version.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

@dosubot[bot] commented on GitHub (Sep 1, 2025): <!-- Greeting --> Hi @jan-arnez! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> This is a known limitation in Better Auth v1.3.7: setting trustedOrigins in your config does not automatically add CORS headers to responses when using toNextJsHandler. As a result, cross-origin requests will fail unless you manually handle CORS in your Next.js middleware or API routes. This is why your current middleware approach works, and why it's still required even with trustedOrigins configured. The recommended workaround is to keep your explicit CORS middleware for the /api/auth routes, where you define allowed origins and set the necessary CORS headers (Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Credentials) for both preflight (OPTIONS) and actual requests. This approach is confirmed by multiple users and maintainers as the current best practice for Next.js setups with Better Auth [see details and example](https://github.com/better-auth/better-auth/issues/4052). trustedOrigins is used for internal security checks, but does not handle CORS headers automatically in Next.js. If you want this to be handled by Better Auth itself, you would need to watch for future updates or contribute to the project. For cross-origin authentication to work smoothly, also ensure your cookie settings include sameSite: 'none' and secure: true, and that all frontend origins are listed in trustedOrigins [reference](https://github.com/better-auth/better-auth/issues/3470). In summary: keep your CORS middleware as you have it now—trustedOrigins alone is not enough for CORS header handling in the current version. <!-- 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/2b86f8d8-7851-4028-ba91-d94fed55b1b9?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/2b86f8d8-7851-4028-ba91-d94fed55b1b9?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/2b86f8d8-7851-4028-ba91-d94fed55b1b9?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/2b86f8d8-7851-4028-ba91-d94fed55b1b9?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/2b86f8d8-7851-4028-ba91-d94fed55b1b9?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/2b86f8d8-7851-4028-ba91-d94fed55b1b9?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/2b86f8d8-7851-4028-ba91-d94fed55b1b9?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&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/4343)
Author
Owner

@himself65 commented on GitHub (Sep 1, 2025):

We don't set CORS for such a handler rightn now

@himself65 commented on GitHub (Sep 1, 2025): We don't set CORS for such a handler rightn now
Author
Owner

@himself65 commented on GitHub (Sep 1, 2025):

But IMO we should set CORS by default if you have trustedOrigins

@himself65 commented on GitHub (Sep 1, 2025): But IMO we should set CORS by default if you have trustedOrigins
Author
Owner

@jan-arnez commented on GitHub (Sep 2, 2025):

Configuring CORS through next.config.ts or middleware is easy enough, but it feels like a no-brainer that if I've already set a trusted origin, CORS should just work for it. It was a bit surprising that it didn't. Hopefully, you guys roll out a feature for that down the line.

@jan-arnez commented on GitHub (Sep 2, 2025): Configuring CORS through next.config.ts or middleware is easy enough, but it feels like a no-brainer that if I've already set a trusted origin, CORS should just work for it. It was a bit surprising that it didn't. Hopefully, you guys roll out a feature for that down the line.
Author
Owner

@inducingchaos commented on GitHub (Jan 19, 2026):

it feels like a no-brainer that if I've already set a trusted origin, CORS should just work for it

+1

@inducingchaos commented on GitHub (Jan 19, 2026): > it feels like a no-brainer that if I've already set a trusted origin, CORS should just work for it +1
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#1820