[GH-ISSUE #5243] OAuth providers fail with state_mismatch error when using POST callbacks #18823

Closed
opened 2026-04-15 17:30:20 -05:00 by GiteaMirror · 12 comments
Owner

Originally created by @echeese on GitHub (Oct 12, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/5243

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Configure Better Auth with Apple as a social provider
  2. Attempt to sign in with Apple
  3. Complete authentication on Apple's page
  4. Observe state_mismatch error after redirect back to application
  5. Check browser DevTools → Network tab → the callback POST request does not include the state cookie

Current vs. Expected behavior

Current behavior:
When using Apple Sign-In (or other OAuth providers that use POST-based callbacks), the authentication fails with a state_mismatch error. The state cookie is set with SameSite=Lax attribute during the OAuth flow, but when Apple redirects back via a POST request, the browser does not send the state cookie (per SameSite=Lax specification). Without the state cookie, the server cannot validate the OAuth state parameter and rejects the request.

Expected behavior:
Apple Sign-In (and other POST-based OAuth providers) should complete successfully and authenticate the user.

What version of Better Auth are you using?

1.3.27

System info

{
  "system": {
    "platform": "linux",
    "arch": "x64",
    "version": "#1 SMP PREEMPT_DYNAMIC Mon, 06 Oct 2025 18:48:29 +0000",
    "release": "6.17.1-arch1-1",
    "cpuCount": 16,
    "cpuModel": "AMD Ryzen 7 5800X 8-Core Processor",
    "totalMemory": "62.72 GB",
    "freeMemory": "43.36 GB"
  },
  "node": {
    "version": "v22.18.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "10.9.3"
  },
  "frameworks": [
    {
      "name": "next",
      "version": "16.0.0-beta.0"
    },
    {
      "name": "react",
      "version": "19.2.0"
    }
  ],
  "databases": [
    {
      "name": "pg",
      "version": "^8.16.3"
    }
  ],
  "betterAuth": {
    "version": "^1.3.27",
    "config": {
      "database": {
        "_events": {},
        "_eventsCount": 0,
        "options": {
          "max": 10,
          "min": 0,
          "maxUses": null,
          "allowExitOnIdle": false,
          "maxLifetimeSeconds": 0,
          "idleTimeoutMillis": 10000
        },
        "_clients": [],
        "_idle": [],
        "_expired": {},
        "_pendingQueue": [],
        "ending": false,
        "ended": false
      },
      "socialProviders": {
        "apple": {}
      },
      "plugins": [
        {
          "name": "next-cookies",
          "config": {
            "id": "next-cookies",
            "hooks": {
              "after": [
                {}
              ]
            }
          }
        }
      ],
      "trustedOrigins": [
        "https://appleid.apple.com"
      ]
    }
  }
}

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

Backend

Auth config (if applicable)

export const auth = betterAuth({
  database: new Pool({
    connectionString: process.env.DATABASE_URL,
  }),
  socialProviders: {
    apple: {
      clientId: process.env.APPLE_CLIENT_ID as string,
      clientSecret: process.env.APPLE_CLIENT_SECRET as string,
    },
  },
  plugins: [nextCookies()],
  trustedOrigins: ["https://appleid.apple.com"],
});

### Workaround

Set `SameSite=None` and `secure=true` in the default cookie attributes:


export const auth = betterAuth({
  advanced: {
    defaultCookieAttributes: {
      sameSite: "none",
      secure: true,
    }
  },
  socialProviders: {
    apple: {
      clientId: process.env.APPLE_CLIENT_ID!,
      clientSecret: process.env.APPLE_CLIENT_SECRET!,
    }
  }
});

Additional context

This issue likely affects any OAuth provider that uses POST-based callbacks (like Apple's). The workaround applies SameSite=None to all cookies, which may have security implications for session cookies that would benefit from maintaining SameSite=Lax CSRF protection.

Originally created by @echeese on GitHub (Oct 12, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/5243 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Configure Better Auth with Apple as a social provider 2. Attempt to sign in with Apple 3. Complete authentication on Apple's page 4. Observe `state_mismatch` error after redirect back to application 5. Check browser DevTools → Network tab → the callback POST request does not include the state cookie ### Current vs. Expected behavior **Current behavior:** When using Apple Sign-In (or other OAuth providers that use POST-based callbacks), the authentication fails with a `state_mismatch` error. The state cookie is set with `SameSite=Lax` attribute during the OAuth flow, but when Apple redirects back via a POST request, the browser does not send the state cookie (per [SameSite=Lax specification](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value)). Without the state cookie, the server cannot validate the OAuth state parameter and rejects the request. **Expected behavior:** Apple Sign-In (and other POST-based OAuth providers) should complete successfully and authenticate the user. ### What version of Better Auth are you using? 1.3.27 ### System info ```bash { "system": { "platform": "linux", "arch": "x64", "version": "#1 SMP PREEMPT_DYNAMIC Mon, 06 Oct 2025 18:48:29 +0000", "release": "6.17.1-arch1-1", "cpuCount": 16, "cpuModel": "AMD Ryzen 7 5800X 8-Core Processor", "totalMemory": "62.72 GB", "freeMemory": "43.36 GB" }, "node": { "version": "v22.18.0", "env": "development" }, "packageManager": { "name": "npm", "version": "10.9.3" }, "frameworks": [ { "name": "next", "version": "16.0.0-beta.0" }, { "name": "react", "version": "19.2.0" } ], "databases": [ { "name": "pg", "version": "^8.16.3" } ], "betterAuth": { "version": "^1.3.27", "config": { "database": { "_events": {}, "_eventsCount": 0, "options": { "max": 10, "min": 0, "maxUses": null, "allowExitOnIdle": false, "maxLifetimeSeconds": 0, "idleTimeoutMillis": 10000 }, "_clients": [], "_idle": [], "_expired": {}, "_pendingQueue": [], "ending": false, "ended": false }, "socialProviders": { "apple": {} }, "plugins": [ { "name": "next-cookies", "config": { "id": "next-cookies", "hooks": { "after": [ {} ] } } } ], "trustedOrigins": [ "https://appleid.apple.com" ] } } } ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript export const auth = betterAuth({ database: new Pool({ connectionString: process.env.DATABASE_URL, }), socialProviders: { apple: { clientId: process.env.APPLE_CLIENT_ID as string, clientSecret: process.env.APPLE_CLIENT_SECRET as string, }, }, plugins: [nextCookies()], trustedOrigins: ["https://appleid.apple.com"], }); ### Workaround Set `SameSite=None` and `secure=true` in the default cookie attributes: export const auth = betterAuth({ advanced: { defaultCookieAttributes: { sameSite: "none", secure: true, } }, socialProviders: { apple: { clientId: process.env.APPLE_CLIENT_ID!, clientSecret: process.env.APPLE_CLIENT_SECRET!, } } }); ``` ### Additional context This issue likely affects any OAuth provider that uses POST-based callbacks (like Apple's). The workaround applies `SameSite=None` to all cookies, which may have security implications for session cookies that would benefit from maintaining `SameSite=Lax` CSRF protection.
GiteaMirror added the locked label 2026-04-15 17:30:20 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Oct 12, 2025):

This issue happens because browsers do not send cookies with SameSite=Lax on cross-site POST requests, which is how Apple and some other OAuth providers deliver their callback. As a result, the state cookie is missing and Better Auth throws a state_mismatch error. The workaround—setting SameSite=None; Secure=true in defaultCookieAttributes—works because it allows the cookie to be sent on these POST callbacks, but it applies to all cookies, which can weaken CSRF protection for session cookies.

Better Auth does support per-cookie configuration, so you can target just the OAuth state cookie for this override instead of applying it globally. In your config, use the advanced.cookies option to set attributes for the state cookie only, like this:

export const auth = betterAuth({
  advanced: {
    cookies: {
      state: {
        attributes: {
          sameSite: "none",
          secure: true,
        }
      }
    }
  },
  // ...other config
});

This way, only the state cookie uses SameSite=None, while your session cookies can keep SameSite=Lax for better CSRF protection. This approach is supported by Better Auth's cookie management system, which allows per-cookie overrides via the advanced.cookies config option source.

To reply, just mention @dosu.


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

<!-- gh-comment-id:3393837130 --> @dosubot[bot] commented on GitHub (Oct 12, 2025): <!-- Answer --> This issue happens because browsers do not send cookies with `SameSite=Lax` on cross-site POST requests, which is how Apple and some other OAuth providers deliver their callback. As a result, the state cookie is missing and Better Auth throws a `state_mismatch` error. The workaround—setting `SameSite=None; Secure=true` in `defaultCookieAttributes`—works because it allows the cookie to be sent on these POST callbacks, but it applies to all cookies, which can weaken CSRF protection for session cookies. Better Auth does support per-cookie configuration, so you can target just the OAuth state cookie for this override instead of applying it globally. In your config, use the `advanced.cookies` option to set attributes for the `state` cookie only, like this: ```typescript export const auth = betterAuth({ advanced: { cookies: { state: { attributes: { sameSite: "none", secure: true, } } } }, // ...other config }); ``` This way, only the state cookie uses `SameSite=None`, while your session cookies can keep `SameSite=Lax` for better CSRF protection. This approach is supported by Better Auth's cookie management system, which allows per-cookie overrides via the `advanced.cookies` config option [source](https://github.com/better-auth/better-auth/blob/63c8f4dd7356a587e4666f1b8004601250f9294d/packages/better-auth/src/cookies/index.ts). <!-- 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/c93b3e1f-ea9a-479e-bff0-d5d1b29faadd?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/c93b3e1f-ea9a-479e-bff0-d5d1b29faadd?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/c93b3e1f-ea9a-479e-bff0-d5d1b29faadd?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/c93b3e1f-ea9a-479e-bff0-d5d1b29faadd?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/c93b3e1f-ea9a-479e-bff0-d5d1b29faadd?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/c93b3e1f-ea9a-479e-bff0-d5d1b29faadd?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/c93b3e1f-ea9a-479e-bff0-d5d1b29faadd?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/5243)
Author
Owner

@tjazsilovsek commented on GitHub (Oct 14, 2025):

+1

Is this bots answer the approach we should take or is it a halucination?

<!-- gh-comment-id:3401776507 --> @tjazsilovsek commented on GitHub (Oct 14, 2025): +1 Is this bots answer the approach we should take or is it a halucination?
Author
Owner

@Nils-Fischer commented on GitHub (Oct 15, 2025):

+1

Is this bots answer the approach we should take or is it a halucination?

The solution definitely worked for me.

<!-- gh-comment-id:3405885488 --> @Nils-Fischer commented on GitHub (Oct 15, 2025): > +1 > > Is this bots answer the approach we should take or is it a halucination? The solution definitely worked for me.
Author
Owner

@DIYgod commented on GitHub (Oct 24, 2025):

This temporary solution doesn't work for Expo. Please help me 😫 @himself65

Additionally, I found that it only occurs in v1.3.28, v1.3.27 is still ok

<!-- gh-comment-id:3441197572 --> @DIYgod commented on GitHub (Oct 24, 2025): This temporary solution doesn't work for Expo. Please help me 😫 @himself65 Additionally, I found that it only occurs in v1.3.28, v1.3.27 is still ok
Author
Owner

@himself65 commented on GitHub (Oct 24, 2025):

I will take a look

<!-- gh-comment-id:3441265292 --> @himself65 commented on GitHub (Oct 24, 2025): I will take a look
Author
Owner

@artur-infigo commented on GitHub (Oct 27, 2025):

Any updates on this one?

<!-- gh-comment-id:3450446580 --> @artur-infigo commented on GitHub (Oct 27, 2025): Any updates on this one?
Author
Owner

@bandhan-majumder commented on GitHub (Oct 30, 2025):

Got the same issue. I fixed this by modifying this variable value from localhost to my domain (www.example.com)

BETTER_AUTH_URL=www.example.com

<!-- gh-comment-id:3467041275 --> @bandhan-majumder commented on GitHub (Oct 30, 2025): Got the same issue. I fixed this by modifying this variable value from localhost to my domain (www.example.com) BETTER_AUTH_URL=www.example.com
Author
Owner

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

Based on your description, it seems the state cookie isn’t being sent on the POST callback because it’s still set with SameSite=Lax. The proven workaround is to set the default cookie attributes to SameSite=None and secure=true (making sure your app is served over HTTPS) so that the state cookie is included on POST requests. This is especially important when your BETTER_AUTH_URL environment variable accurately reflects your domain (e.g., www.example.com) rather than localhost, as you discovered.

Next steps:

  1. Confirm that BETTER_AUTH_URL is set to your production domain (www.example.com) across your configuration.
  2. Update your Better Auth initialization to use the advanced cookie options:
      • Set sameSite: "none" and secure: true.
  3. Verify that your production environment serves the application over HTTPS; otherwise, secure cookies will not be sent.
  4. Test the complete OAuth flow (Apple Sign-In or other POST-based providers) and monitor the browser’s network tab to ensure the state cookie is sent on the callback request.
  5. Be aware that applying SameSite=None to all cookies might reduce CSRF protection for session cookies, so consider if you need to scope this change only to the state cookie in the future.

These steps should address the state_mismatch issue caused by missing state cookies during POST callbacks [1], [2].

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3467042995 --> @better-auth-agent[bot] commented on GitHub (Oct 30, 2025): Based on your description, it seems the state cookie isn’t being sent on the POST callback because it’s still set with SameSite=Lax. The proven workaround is to set the default cookie attributes to SameSite=None and secure=true (making sure your app is served over HTTPS) so that the state cookie is included on POST requests. This is especially important when your BETTER_AUTH_URL environment variable accurately reflects your domain (e.g., www.example.com) rather than localhost, as you discovered. Next steps: 1. Confirm that BETTER_AUTH_URL is set to your production domain (www.example.com) across your configuration. 2. Update your Better Auth initialization to use the advanced cookie options:   • Set sameSite: "none" and secure: true. 3. Verify that your production environment serves the application over HTTPS; otherwise, secure cookies will not be sent. 4. Test the complete OAuth flow (Apple Sign-In or other POST-based providers) and monitor the browser’s network tab to ensure the state cookie is sent on the callback request. 5. Be aware that applying SameSite=None to all cookies might reduce CSRF protection for session cookies, so consider if you need to scope this change only to the state cookie in the future. These steps should address the state_mismatch issue caused by missing state cookies during POST callbacks [[1]](https://github.com/better-auth/better-auth/issues/5243), [[2]](https://github.com/better-auth/better-auth/pull/5322). <!-- 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

@artur-infigo commented on GitHub (Oct 30, 2025):

Got the same issue. I fixed this by modifying this variable value from localhost to my domain (www.example.com)

BETTER_AUTH_URL=www.example.com

Which version of Better‑Auth do you use?

<!-- gh-comment-id:3467056779 --> @artur-infigo commented on GitHub (Oct 30, 2025): > Got the same issue. I fixed this by modifying this variable value from localhost to my domain ([www.example.com](http://www.example.com)) > > BETTER_AUTH_URL=www.example.com Which version of Better‑Auth do you use?
Author
Owner

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

It sounds like you're running into the well‑known cookie issue where using the default SameSite=Lax prevents the state cookie from being sent on POST callbacks. For your version (1.3.27), the recommended workaround is two‑fold:

  1. Update the default cookie attributes to use SameSite "none" and secure set to true. This ensures that the state cookie is sent with the POST callback, allowing the OAuth state to be validated correctly [1].

  2. Make sure that your BETTER_AUTH_URL value is set to your domain (e.g., www.example.com) rather than localhost. This change can resolve issues with domain mismatches that prevent cookies from being sent [2].

Keep in mind that applying SameSite "none" to all cookies may reduce CSRF protection for some session cookies. As an extra step, consider if it’s possible to scope this change only to the OAuth state cookie without impacting others.

Try these steps and let us know if the issue persists.

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3467058138 --> @better-auth-agent[bot] commented on GitHub (Oct 30, 2025): It sounds like you're running into the well‑known cookie issue where using the default SameSite=Lax prevents the state cookie from being sent on POST callbacks. For your version (1.3.27), the recommended workaround is two‑fold: 1. Update the default cookie attributes to use SameSite "none" and secure set to true. This ensures that the state cookie is sent with the POST callback, allowing the OAuth state to be validated correctly [[1]](https://github.com/better-auth/better-auth/issues/5243). 2. Make sure that your BETTER_AUTH_URL value is set to your domain (e.g., www.example.com) rather than localhost. This change can resolve issues with domain mismatches that prevent cookies from being sent [[2]](https://github.com/better-auth/better-auth/pull/5322). Keep in mind that applying SameSite "none" to all cookies may reduce CSRF protection for some session cookies. As an extra step, consider if it’s possible to scope this change only to the OAuth state cookie without impacting others. Try these steps and let us know if the issue persists. <!-- 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

@jtomaszewski commented on GitHub (Nov 19, 2025):

The fix from #5748 seem to have been reverted, so it's probably not fixed yet?

<!-- gh-comment-id:3554712372 --> @jtomaszewski commented on GitHub (Nov 19, 2025): The fix from #5748 seem to have been [reverted](https://github.com/better-auth/better-auth/commit/956eb5b015417ae6cc1a7f78891ce9e81539c2f2), so it's probably not fixed yet?
Author
Owner

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

The fix from #5748 seem to have been reverted, so it's probably not fixed yet?

Yeah, still not working for me. Using v1.4.1

<!-- gh-comment-id:3568601738 --> @donpro2009 commented on GitHub (Nov 24, 2025): > The fix from [#5748](https://github.com/better-auth/better-auth/pull/5748) seem to have been [reverted](https://github.com/better-auth/better-auth/commit/956eb5b015417ae6cc1a7f78891ce9e81539c2f2), so it's probably not fixed yet? Yeah, still not working for me. Using v1.4.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#18823