Oauth Flow state_mismatch & state_not_found issues #2360

Closed
opened 2026-03-13 09:46:54 -05:00 by GiteaMirror · 11 comments
Owner

Originally created by @AntoninMarchardDev on GitHub (Nov 22, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Backend

I'm using nest JS with the community package @thallesp/nestjs-better-auth

My auth config is the following :

const authConfig: BetterAuthOptions = {
  database: prismaAdapter(prisma, {
    provider: 'postgresql',
  }),
  user: {
    fields: {
      name: 'username',
      image: 'avatar',
    },
  },
  socialProviders: {
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      disableImplicitSignUp: true,
    },
    apple: {
      clientId: process.env.APPLE_CLIENT_ID as string,
      clientSecret: process.env.APPLE_CLIENT_SECRET as string,
      appBundleIdentifier: process.env.APPLE_APP_BUNDLE_IDENTIFIER as string,
      disableImplicitSignUp: true,
    },
  },
  emailAndPassword: {
    enabled: true,
  },
  trustedOrigins: [
    'https://appleid.apple.com',
    process.env.FRONT_URL,
    process.env.ADMIN_URL,
    process.env.APP_SCHEME,
  ],
  advanced: {
    database: {
      generateId: () => randomUUID(),
    },
    disableOriginCheck: true,
    // crossSubDomainCookies: {
    //   enabled: true,
    // },
    cookies: {
      state: {
        attributes: {
          sameSite: 'none',
          secure: true,
        },
      },
    },
    useSecureCookies: true,
    defaultCookieAttributes: {
      secure: true,
      sameSite: 'none',
      httpOnly: true,
      domain: undefined,
    },
  },
  plugins: [
    customSession(async ({ user, session }) => {
      const roles = await prisma.user.findUnique({
        where: { id: user.id },
        select: {
          role: true,
        },
      });
      return {
        user: {
          ...user,
          role: roles?.role,
        },
        session,
      };
    }),
    emailOTP({
      async sendVerificationOTP({ email, otp, type }) {
        if (type === 'email-verification') {
          console.log('EMAIL VERIFICATION OTP :', otp);
        } else if (type === 'sign-in') {
        } else {
          console.log('RESET PASSWORD OTP :', otp);
        }
      },
    }),
  ],
  hooks: {
    before: createAuthMiddleware(async (ctx) => {
      ctx.context.password.hash = hashPassword;
      ctx.context.password.verify = verifyPassword;
    }),
  },
} satisfies BetterAuthOptions;

/**
 * export the auth provider with the correct type
 */
export const auth = betterAuth(authConfig) as ReturnType<
  typeof betterAuth<typeof authConfig>
>;

Frontend, expo :

import { createAuthClient } from "better-auth/react";
import { expoClient } from "@better-auth/expo/client";
import { emailOTPClient } from "better-auth/client/plugins";
import Constants from "expo-constants";
import { secureStoreAdapter } from "./secure-store-adapter";

export const authClient = createAuthClient({
  baseURL: process.env.EXPO_PUBLIC_MANAGER_BASE_URL,
  plugins: [
    expoClient({
      scheme: Constants.expoConfig?.scheme as string,
      storagePrefix: Constants.expoConfig?.scheme as string,
      storage: secureStoreAdapter,
    }),
    emailOTPClient(),
  ],
});

I first tried using the authClient.signIn flow :

authClient.signIn.social({
        provider: "apple",
        callbackURL: scheme,
      });

But too much issues. So I did this workaround :

const scheme =
        (Constants.expoConfig?.scheme as string) + "://" ||
        "myapp//";
      const baseURL = process.env.EXPO_PUBLIC_MANAGER_BASE_URL + "/api/auth";

      const apiUrl = `${baseURL}/sign-in/social`;

      const response = await fetch(apiUrl, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          provider: "apple",
          callbackURL: scheme,
        }),
      });

      const json = await response.json();

      if (!json.url && !json.redirect) {
        return;
      }

      const googleAuthUrl = json.url || json.redirect || json.data?.url;

      const result = await WebBrowser.openAuthSessionAsync(
        googleAuthUrl,
        scheme,
      );

      console.log("result", result);

      if (result.type === "success") {
        await authClient.getSession();
      }

Current vs. Expected behavior

Current Behaviour

For google oauth -> state_mismatch
For apple oauth - state_not_found

I should have a successful login or registration.

What version of Better Auth are you using?

1.4.0

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 25.1.0: Mon Oct 20 19:33:36 PDT 2025; root:xnu-12377.41.6~2/RELEASE_ARM64_T6030",
    "release": "25.1.0",
    "cpuCount": 11,
    "cpuModel": "Apple M3 Pro",
    "totalMemory": "36.00 GB",
    "freeMemory": "0.11 GB"
  },
  "node": {
    "version": "v22.0.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "10.5.1"
  },
  "frameworks": null,
  "databases": [
    {
      "name": "@prisma/client",
      "version": "6.3.1"
    }
  ],
  "betterAuth": {
    "version": "1.4.0",
    "config": null
  }
}

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 @AntoninMarchardDev on GitHub (Nov 22, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce ### Backend I'm using nest JS with the community package @thallesp/nestjs-better-auth My auth config is the following : ```js const authConfig: BetterAuthOptions = { database: prismaAdapter(prisma, { provider: 'postgresql', }), user: { fields: { name: 'username', image: 'avatar', }, }, socialProviders: { google: { clientId: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET, disableImplicitSignUp: true, }, apple: { clientId: process.env.APPLE_CLIENT_ID as string, clientSecret: process.env.APPLE_CLIENT_SECRET as string, appBundleIdentifier: process.env.APPLE_APP_BUNDLE_IDENTIFIER as string, disableImplicitSignUp: true, }, }, emailAndPassword: { enabled: true, }, trustedOrigins: [ 'https://appleid.apple.com', process.env.FRONT_URL, process.env.ADMIN_URL, process.env.APP_SCHEME, ], advanced: { database: { generateId: () => randomUUID(), }, disableOriginCheck: true, // crossSubDomainCookies: { // enabled: true, // }, cookies: { state: { attributes: { sameSite: 'none', secure: true, }, }, }, useSecureCookies: true, defaultCookieAttributes: { secure: true, sameSite: 'none', httpOnly: true, domain: undefined, }, }, plugins: [ customSession(async ({ user, session }) => { const roles = await prisma.user.findUnique({ where: { id: user.id }, select: { role: true, }, }); return { user: { ...user, role: roles?.role, }, session, }; }), emailOTP({ async sendVerificationOTP({ email, otp, type }) { if (type === 'email-verification') { console.log('EMAIL VERIFICATION OTP :', otp); } else if (type === 'sign-in') { } else { console.log('RESET PASSWORD OTP :', otp); } }, }), ], hooks: { before: createAuthMiddleware(async (ctx) => { ctx.context.password.hash = hashPassword; ctx.context.password.verify = verifyPassword; }), }, } satisfies BetterAuthOptions; /** * export the auth provider with the correct type */ export const auth = betterAuth(authConfig) as ReturnType< typeof betterAuth<typeof authConfig> >; ``` Frontend, expo : ```js import { createAuthClient } from "better-auth/react"; import { expoClient } from "@better-auth/expo/client"; import { emailOTPClient } from "better-auth/client/plugins"; import Constants from "expo-constants"; import { secureStoreAdapter } from "./secure-store-adapter"; export const authClient = createAuthClient({ baseURL: process.env.EXPO_PUBLIC_MANAGER_BASE_URL, plugins: [ expoClient({ scheme: Constants.expoConfig?.scheme as string, storagePrefix: Constants.expoConfig?.scheme as string, storage: secureStoreAdapter, }), emailOTPClient(), ], }); ``` I first tried using the `authClient.signIn` flow : ```js authClient.signIn.social({ provider: "apple", callbackURL: scheme, }); ``` But too much issues. So I did this workaround : ```js const scheme = (Constants.expoConfig?.scheme as string) + "://" || "myapp//"; const baseURL = process.env.EXPO_PUBLIC_MANAGER_BASE_URL + "/api/auth"; const apiUrl = `${baseURL}/sign-in/social`; const response = await fetch(apiUrl, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ provider: "apple", callbackURL: scheme, }), }); const json = await response.json(); if (!json.url && !json.redirect) { return; } const googleAuthUrl = json.url || json.redirect || json.data?.url; const result = await WebBrowser.openAuthSessionAsync( googleAuthUrl, scheme, ); console.log("result", result); if (result.type === "success") { await authClient.getSession(); } ``` ### Current vs. Expected behavior ### Current Behaviour For google oauth -> `state_mismatch` For apple oauth - `state_not_found` ### I should have a successful login or registration. ### What version of Better Auth are you using? 1.4.0 ### System info ```bash { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 25.1.0: Mon Oct 20 19:33:36 PDT 2025; root:xnu-12377.41.6~2/RELEASE_ARM64_T6030", "release": "25.1.0", "cpuCount": 11, "cpuModel": "Apple M3 Pro", "totalMemory": "36.00 GB", "freeMemory": "0.11 GB" }, "node": { "version": "v22.0.0", "env": "development" }, "packageManager": { "name": "npm", "version": "10.5.1" }, "frameworks": null, "databases": [ { "name": "@prisma/client", "version": "6.3.1" } ], "betterAuth": { "version": "1.4.0", "config": null } } ``` ### 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 expo label 2026-03-13 09:46:54 -05:00
Author
Owner

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

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

DiagramDiscordGitHub

Diagram Join Star

@better-auth-agent[bot] commented on GitHub (Nov 22, 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

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

Your state_mismatch and state_not_found errors are almost certainly due to how OAuth state is managed in mobile/Expo flows. Better Auth, by default, stores the OAuth state in a cookie, but mobile deep links (like those used in Expo) can't reliably send or receive cookies during the OAuth redirect, which leads to these errors. This is a common pitfall for Expo and React Native OAuth integrations.

Recent Better Auth releases introduced fixes for Expo/mobile flows: the Expo client and backend now use an OAuth proxy plugin that wraps the state and cookie in an encrypted bundle, allowing the callback to validate state without relying on browser cookies. This requires using the Expo client’s built-in OAuth flow, not a manual POST + WebBrowser workaround, and having both backend and frontend on the latest versions with the fixes for cookie merging, trustedOrigins, and sameSite handling (source, source, source).

To resolve this:

  • Make sure you are using the latest versions of both better-auth and @better-auth/expo/client.
  • Use the Expo client’s authClient.signIn.social method for social login, not a manual POST + WebBrowser workaround.
  • Ensure your backend config includes trustedOrigins for exp:// and https://appleid.apple.com, and that your cookie attributes for state use sameSite: 'none' and secure: true.
  • If you need to pass additional data through the OAuth flow, use the documented additionalData option on the client and enable it in your backend config (source).

If you still see issues after updating and using the recommended flow, double-check that your callback URLs and schemes match exactly between frontend and backend, and that your Expo client storage adapter is working as expected. The manual workaround you tried is not compatible with Better Auth’s state management for mobile and will almost always result in state errors.

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 (Nov 22, 2025): <!-- Answer --> Your `state_mismatch` and `state_not_found` errors are almost certainly due to how OAuth state is managed in mobile/Expo flows. Better Auth, by default, stores the OAuth state in a cookie, but mobile deep links (like those used in Expo) can't reliably send or receive cookies during the OAuth redirect, which leads to these errors. This is a common pitfall for Expo and React Native OAuth integrations. Recent Better Auth releases introduced fixes for Expo/mobile flows: the Expo client and backend now use an OAuth proxy plugin that wraps the state and cookie in an encrypted bundle, allowing the callback to validate state without relying on browser cookies. This requires using the Expo client’s built-in OAuth flow, not a manual POST + WebBrowser workaround, and having both backend and frontend on the latest versions with the fixes for cookie merging, trustedOrigins, and sameSite handling ([source](https://github.com/better-auth/better-auth/pull/5748), [source](https://github.com/better-auth/better-auth/pull/5836), [source](https://github.com/better-auth/better-auth/pull/5759)). To resolve this: - Make sure you are using the latest versions of both better-auth and @better-auth/expo/client. - Use the Expo client’s `authClient.signIn.social` method for social login, not a manual POST + WebBrowser workaround. - Ensure your backend config includes `trustedOrigins` for `exp://` and `https://appleid.apple.com`, and that your cookie attributes for state use `sameSite: 'none'` and `secure: true`. - If you need to pass additional data through the OAuth flow, use the documented `additionalData` option on the client and enable it in your backend config ([source](https://github.com/better-auth/better-auth/pull/5742)). If you still see issues after updating and using the recommended flow, double-check that your callback URLs and schemes match exactly between frontend and backend, and that your Expo client storage adapter is working as expected. The manual workaround you tried is not compatible with Better Auth’s state management for mobile and will almost always result in state errors. <!-- 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/30c8442b-392f-401f-9842-8e004445ee99?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/30c8442b-392f-401f-9842-8e004445ee99?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/30c8442b-392f-401f-9842-8e004445ee99?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/30c8442b-392f-401f-9842-8e004445ee99?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/30c8442b-392f-401f-9842-8e004445ee99?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/30c8442b-392f-401f-9842-8e004445ee99?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/30c8442b-392f-401f-9842-8e004445ee99?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%20%60state_mismatch%60%20and%20%60state_not_found%60%20errors%20are%20almost%20certainly%20due%20to%20how%20OAuth%20state%20is%20managed%20in%20mobile/Expo%20flows.%20Better%20Auth%2C%20by%20default%2C%20stores%20the%20OAuth%20state%20in%20a%20cookie%2C%20but%20mobile%20deep%20links%20%28like%20those%20used%20in%20Expo%29%20can%27t%20reliably%20send%20or%20receive%20cookies%20during%20the%20OAuth%20redirect%2C%20which%20leads%20to%20these%20errors.%20This%20is%20a%20common%20pitfall%20for%20Expo%20and%20React%20Native%20OAuth%20integrations.%0A%0ARecent%20Better%20Auth%20releases%20introduced%20fixes%20for%20Expo/mobile%20flows%3A%20the%20Expo%20client%20and%20backend%20now%20use%20an%20OAuth%20proxy%20plugin%20that%20wraps%20the%20state%20and%20cookie%20in%20an%20encrypted%20bundle%2C%20allowing%20the%20callback%20to%20validate%20state%20without%20relying%20on%20browser%20cookies.%20This%20requires%20using%20the%20Expo%20client%E2%80%99s%20built-in%20OAuth%20flow%2C%20not%20a%20manual%20POST%20%2B%20WebBrowser%20workaround%2C%20and%20having%20both%20backend%20and%20frontend%20on%20the%20latest%20versions%20with%20the%20fixes%20for%20cookie%20merging%2C%20trustedOrigins%2C%20and%20sameSite%20handling%20%28%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/pull/5748%29%2C%20%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/pull/5836%29%2C%20%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/pull/5759%29%29.%0A%0ATo%20resolve%20this%3A%0A%0A-%20Make%20sure%20you%20are%20using%20the%20latest%20versions%20of%20both%20better-auth%20and%20%40better-auth/expo/client.%0A-%20Use%20the%20Expo%20client%E2%80%99s%20%60authClient.signIn.social%60%20method%20for%20social%20login%2C%20not%20a%20manual%20POST%20%2B%20WebBrowser%20workaround.%0A-%20Ensure%20your%20backend%20config%20includes%20%60trustedOrigins%60%20for%20%60exp%3A//%60%20and%20%60https%3A//appleid.apple.com%60%2C%20and%20that%20your%20cookie%20attributes%20for%20state%20use%20%60sameSite%3A%20%27none%27%60%20and%20%60secure%3A%20true%60.%0A-%20If%20you%20need%20to%20pass%20additional%20data%20through%20the%20OAuth%20flow%2C%20use%20the%20documented%20%60additionalData%60%20option%20on%20the%20client%20and%20enable%20it%20in%20your%20backend%20config%20%28%5Bsource%5D%28https%3A//github.com/better-auth/better-auth/pull/5742%29%29.%0A%0AIf%20you%20still%20see%20issues%20after%20updating%20and%20using%20the%20recommended%20flow%2C%20double-check%20that%20your%20callback%20URLs%20and%20schemes%20match%20exactly%20between%20frontend%20and%20backend%2C%20and%20that%20your%20Expo%20client%20storage%20adapter%20is%20working%20as%20expected.%20The%20manual%20workaround%20you%20tried%20is%20not%20compatible%20with%20Better%20Auth%E2%80%99s%20state%20management%20for%20mobile%20and%20will%20almost%20always%20result%20in%20state%20errors.)&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/6207)
Author
Owner

@AntoninMarchardDev commented on GitHub (Nov 22, 2025):

@dosu

I have the same issue with my Next JS application, v1.4.0 using authClient.signIn.social
Also, I've tried using the authClient on expo, and I receive an expo-authorization-proxy file (0 ko) ...

@AntoninMarchardDev commented on GitHub (Nov 22, 2025): @dosu I have the same issue with my Next JS application, v1.4.0 using `authClient.signIn.social` Also, I've tried using the `authClient` on expo, and I receive an expo-authorization-proxy file (0 ko) ...
Author
Owner

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

The root cause of your state_mismatch and state_not_found errors in both Next.js and Expo is almost certainly due to how Better Auth v1.4.0 manages OAuth state with cookies, which can break in cross-origin and mobile flows. For Expo, the expo-authorization-proxy file being 0 ko means the proxy endpoint failed to return a valid redirect—usually because of a misconfiguration or a missing/invalid state parameter, so the OAuth handshake can't complete.

Recent Better Auth releases (after v1.4.0) fixed several issues: they set sameSite: 'none' on state cookies, improved trustedOrigins handling (including exp:// and https://appleid.apple.com), and enhanced the OAuth proxy plugin to reliably pass state and cookies through mobile and cross-origin flows. The Expo client also now merges cookies instead of overwriting them, which is critical for session continuity. These changes are required for both Next.js and Expo to avoid state errors and proxy failures (details, expo proxy logic, proxy plugin).

To resolve this:

  • Upgrade both better-auth and @better-auth/expo/client to the latest versions (these fixes landed after v1.4.0).
  • Make sure your backend config includes trustedOrigins for exp:// and https://appleid.apple.com, and that all state cookies use sameSite: 'none' and secure: true.
  • Use authClient.signIn.social for Expo and Next.js—avoid manual POST + WebBrowser workarounds.
  • If you still see issues, check your network requests for missing/incorrect cookies, inspect callback URLs for mismatches, and review server logs for state/cookie errors.

If you want, share your Next.js and Expo auth configs and I can help spot any subtle misconfigurations.

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 (Nov 22, 2025): <!-- Answer --> The root cause of your state_mismatch and state_not_found errors in both Next.js and Expo is almost certainly due to how Better Auth v1.4.0 manages OAuth state with cookies, which can break in cross-origin and mobile flows. For Expo, the expo-authorization-proxy file being 0 ko means the proxy endpoint failed to return a valid redirect—usually because of a misconfiguration or a missing/invalid state parameter, so the OAuth handshake can't complete. Recent Better Auth releases (after v1.4.0) fixed several issues: they set sameSite: 'none' on state cookies, improved trustedOrigins handling (including exp:// and https://appleid.apple.com), and enhanced the OAuth proxy plugin to reliably pass state and cookies through mobile and cross-origin flows. The Expo client also now merges cookies instead of overwriting them, which is critical for session continuity. These changes are required for both Next.js and Expo to avoid state errors and proxy failures ([details](https://github.com/better-auth/better-auth/pull/5748), [expo proxy logic](https://github.com/better-auth/better-auth/blob/44308bf20d431bd943a92a2ed4204a6b7d5a4182/packages/expo/src/index.ts#L21-L121), [proxy plugin](https://github.com/better-auth/better-auth/blob/44308bf20d431bd943a92a2ed4204a6b7d5a4182/packages/better-auth/src/plugins/oauth-proxy/index.ts#L39-L497)). To resolve this: - Upgrade both better-auth and @better-auth/expo/client to the latest versions (these fixes landed after v1.4.0). - Make sure your backend config includes trustedOrigins for exp:// and https://appleid.apple.com, and that all state cookies use sameSite: 'none' and secure: true. - Use authClient.signIn.social for Expo and Next.js—avoid manual POST + WebBrowser workarounds. - If you still see issues, check your network requests for missing/incorrect cookies, inspect callback URLs for mismatches, and review server logs for state/cookie errors. If you want, share your Next.js and Expo auth configs and I can help spot any subtle misconfigurations. <!-- 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/c99f2e0e-516e-48f6-b048-248417574c7a?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/c99f2e0e-516e-48f6-b048-248417574c7a?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/c99f2e0e-516e-48f6-b048-248417574c7a?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/c99f2e0e-516e-48f6-b048-248417574c7a?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/c99f2e0e-516e-48f6-b048-248417574c7a?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/c99f2e0e-516e-48f6-b048-248417574c7a?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/c99f2e0e-516e-48f6-b048-248417574c7a?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=The%20root%20cause%20of%20your%20state_mismatch%20and%20state_not_found%20errors%20in%20both%20Next.js%20and%20Expo%20is%20almost%20certainly%20due%20to%20how%20Better%20Auth%20v1.4.0%20manages%20OAuth%20state%20with%20cookies%2C%20which%20can%20break%20in%20cross-origin%20and%20mobile%20flows.%20For%20Expo%2C%20the%20expo-authorization-proxy%20file%20being%200%20ko%20means%20the%20proxy%20endpoint%20failed%20to%20return%20a%20valid%20redirect%E2%80%94usually%20because%20of%20a%20misconfiguration%20or%20a%20missing/invalid%20state%20parameter%2C%20so%20the%20OAuth%20handshake%20can%27t%20complete.%0A%0ARecent%20Better%20Auth%20releases%20%28after%20v1.4.0%29%20fixed%20several%20issues%3A%20they%20set%20sameSite%3A%20%27none%27%20on%20state%20cookies%2C%20improved%20trustedOrigins%20handling%20%28including%20exp%3A//%20and%20https%3A//appleid.apple.com%29%2C%20and%20enhanced%20the%20OAuth%20proxy%20plugin%20to%20reliably%20pass%20state%20and%20cookies%20through%20mobile%20and%20cross-origin%20flows.%20The%20Expo%20client%20also%20now%20merges%20cookies%20instead%20of%20overwriting%20them%2C%20which%20is%20critical%20for%20session%20continuity.%20These%20changes%20are%20required%20for%20both%20Next.js%20and%20Expo%20to%20avoid%20state%20errors%20and%20proxy%20failures%20%28%5Bdetails%5D%28https%3A//github.com/better-auth/better-auth/pull/5748%29%2C%20%5Bexpo%20proxy%20logic%5D%28https%3A//github.com/better-auth/better-auth/blob/44308bf20d431bd943a92a2ed4204a6b7d5a4182/packages/expo/src/index.ts%23L21-L121%29%2C%20%5Bproxy%20plugin%5D%28https%3A//github.com/better-auth/better-auth/blob/44308bf20d431bd943a92a2ed4204a6b7d5a4182/packages/better-auth/src/plugins/oauth-proxy/index.ts%23L39-L497%29%29.%0A%0ATo%20resolve%20this%3A%0A-%20Upgrade%20both%20better-auth%20and%20%40better-auth/expo/client%20to%20the%20latest%20versions%20%28these%20fixes%20landed%20after%20v1.4.0%29.%0A-%20Make%20sure%20your%20backend%20config%20includes%20trustedOrigins%20for%20exp%3A//%20and%20https%3A//appleid.apple.com%2C%20and%20that%20all%20state%20cookies%20use%20sameSite%3A%20%27none%27%20and%20secure%3A%20true.%0A-%20Use%20authClient.signIn.social%20for%20Expo%20and%20Next.js%E2%80%94avoid%20manual%20POST%20%2B%20WebBrowser%20workarounds.%0A-%20If%20you%20still%20see%20issues%2C%20check%20your%20network%20requests%20for%20missing/incorrect%20cookies%2C%20inspect%20callback%20URLs%20for%20mismatches%2C%20and%20review%20server%20logs%20for%20state/cookie%20errors.%0A%0AIf%20you%20want%2C%20share%20your%20Next.js%20and%20Expo%20auth%20configs%20and%20I%20can%20help%20spot%20any%20subtle%20misconfigurations.)&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/6207)
Author
Owner

@AntoninMarchardDev commented on GitHub (Nov 22, 2025):

@dosu @better-auth-agent

Everything is already upgraded to v1.4.0
My Backend config is correct, logged everything and everything is fine
I am using authClient.signIn.social everywhere now

Don't understand why on expo I've got a call to /api/auth/expo-authorization-proxy

@AntoninMarchardDev commented on GitHub (Nov 22, 2025): @dosu @better-auth-agent Everything is already upgraded to v1.4.0 My Backend config is correct, logged everything and everything is fine I am using authClient.signIn.social everywhere now Don't understand why on expo I've got a call to /api/auth/expo-authorization-proxy
Author
Owner

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

Hi there, thanks for reaching out! The state_mismatch and state_not_found errors typically indicate that the state cookie managed during the OAuth flow isn’t being carried over or recognized properly. Since you’re using Expo with our authorization proxy endpoint, here are a few troubleshooting steps:

• Verify that the callback URL scheme is consistent between your mobile app and your backend. Any slight mismatch in protocol or scheme can cause the state cookie not to be found or validated [1].
• Ensure that your cookie settings (sameSite, secure, httpOnly) are properly aligned for the mobile environment. In mobile contexts, sometimes the cookie domain and secure settings need adjustments so that the state cookie isn’t dropped [2].
• Check if your Expo configuration (including the expoClient storage configuration) is correctly saving and forwarding the cookies for the OAuth session. The appearance of the /api/auth/expo-authorization-proxy endpoint is expected when using Expo—it handles the OAuth redirect and state management on your behalf [3].
• Finally, since you’re on v1.4.0 and using authClient.signIn.social everywhere now, double-check that there’s no caching or misrouted proxy behavior interfering with the state cookies.

Do you see the issue consistently only on Expo builds, or does it sometimes appear in other environments too? Let us know so we can help narrow it down further. Tag @better-auth-agent if you would like an answer from the Agent.

DiagramDiscordGitHub

Diagram Join Star

@better-auth-agent[bot] commented on GitHub (Nov 22, 2025): Hi there, thanks for reaching out! The state_mismatch and state_not_found errors typically indicate that the state cookie managed during the OAuth flow isn’t being carried over or recognized properly. Since you’re using Expo with our authorization proxy endpoint, here are a few troubleshooting steps: • Verify that the callback URL scheme is consistent between your mobile app and your backend. Any slight mismatch in protocol or scheme can cause the state cookie not to be found or validated [[1]](https://github.com/better-auth/better-auth/issues/4125). • Ensure that your cookie settings (sameSite, secure, httpOnly) are properly aligned for the mobile environment. In mobile contexts, sometimes the cookie domain and secure settings need adjustments so that the state cookie isn’t dropped [[2]](https://www.better-auth.com/). • Check if your Expo configuration (including the expoClient storage configuration) is correctly saving and forwarding the cookies for the OAuth session. The appearance of the /api/auth/expo-authorization-proxy endpoint is expected when using Expo—it handles the OAuth redirect and state management on your behalf [[3]](https://www.better-auth.com/docs/basic-usage). • Finally, since you’re on v1.4.0 and using authClient.signIn.social everywhere now, double-check that there’s no caching or misrouted proxy behavior interfering with the state cookies. Do you see the issue consistently only on Expo builds, or does it sometimes appear in other environments too? Let us know so we can help narrow it down further. 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 22, 2025):

The call to /api/auth/expo-authorization-proxy is expected and required for Expo OAuth flows in Better Auth. This endpoint is triggered automatically by the Expo client when you use authClient.signIn.social. Its job is to set the OAuth state cookie server-side before redirecting to the provider, which works around the fact that mobile browsers and deep links can't reliably handle cookies during OAuth redirects. This is how Better Auth securely manages state and cookies for mobile OAuth flows—it's not a misconfiguration or error on your part.

If you ever see the proxy endpoint returning a 0 ko file or failing to redirect, it usually means something went wrong with the state parameter, trustedOrigins, or callback URL, so double-check those if you hit issues. But the presence of the /expo-authorization-proxy call itself is normal and required for Expo social login to work correctly.
Reference: Expo proxy implementation | Expo client logic

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 (Nov 22, 2025): <!-- Answer --> The call to /api/auth/expo-authorization-proxy is expected and required for Expo OAuth flows in Better Auth. This endpoint is triggered automatically by the Expo client when you use authClient.signIn.social. Its job is to set the OAuth state cookie server-side before redirecting to the provider, which works around the fact that mobile browsers and deep links can't reliably handle cookies during OAuth redirects. This is how Better Auth securely manages state and cookies for mobile OAuth flows—it's not a misconfiguration or error on your part. If you ever see the proxy endpoint returning a 0 ko file or failing to redirect, it usually means something went wrong with the state parameter, trustedOrigins, or callback URL, so double-check those if you hit issues. But the presence of the /expo-authorization-proxy call itself is normal and required for Expo social login to work correctly. [Reference: Expo proxy implementation](https://github.com/better-auth/better-auth/blob/69c756a4101edf805e82f0cd5841a9dbb594c750/packages/expo/src/index.ts#L88-L119) | [Expo client logic](https://github.com/better-auth/better-auth/blob/69c756a4101edf805e82f0cd5841a9dbb594c750/packages/expo/src/client.ts#L297-L415) <!-- 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/113bc95e-796f-4d7d-9bd9-f1b21ed7f0eb?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/113bc95e-796f-4d7d-9bd9-f1b21ed7f0eb?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/113bc95e-796f-4d7d-9bd9-f1b21ed7f0eb?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/113bc95e-796f-4d7d-9bd9-f1b21ed7f0eb?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/113bc95e-796f-4d7d-9bd9-f1b21ed7f0eb?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/113bc95e-796f-4d7d-9bd9-f1b21ed7f0eb?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/113bc95e-796f-4d7d-9bd9-f1b21ed7f0eb?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=The%20call%20to%20/api/auth/expo-authorization-proxy%20is%20expected%20and%20required%20for%20Expo%20OAuth%20flows%20in%20Better%20Auth.%20This%20endpoint%20is%20triggered%20automatically%20by%20the%20Expo%20client%20when%20you%20use%20authClient.signIn.social.%20Its%20job%20is%20to%20set%20the%20OAuth%20state%20cookie%20server-side%20before%20redirecting%20to%20the%20provider%2C%20which%20works%20around%20the%20fact%20that%20mobile%20browsers%20and%20deep%20links%20can%27t%20reliably%20handle%20cookies%20during%20OAuth%20redirects.%20This%20is%20how%20Better%20Auth%20securely%20manages%20state%20and%20cookies%20for%20mobile%20OAuth%20flows%E2%80%94it%27s%20not%20a%20misconfiguration%20or%20error%20on%20your%20part.%20%0A%0AIf%20you%20ever%20see%20the%20proxy%20endpoint%20returning%20a%200%20ko%20file%20or%20failing%20to%20redirect%2C%20it%20usually%20means%20something%20went%20wrong%20with%20the%20state%20parameter%2C%20trustedOrigins%2C%20or%20callback%20URL%2C%20so%20double-check%20those%20if%20you%20hit%20issues.%20But%20the%20presence%20of%20the%20/expo-authorization-proxy%20call%20itself%20is%20normal%20and%20required%20for%20Expo%20social%20login%20to%20work%20correctly.%20%20%0A%5BReference%3A%20Expo%20proxy%20implementation%5D%28https%3A//github.com/better-auth/better-auth/blob/69c756a4101edf805e82f0cd5841a9dbb594c750/packages/expo/src/index.ts%23L88-L119%29%20%7C%20%5BExpo%20client%20logic%5D%28https%3A//github.com/better-auth/better-auth/blob/69c756a4101edf805e82f0cd5841a9dbb594c750/packages/expo/src/client.ts%23L297-L415%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/6207)
Author
Owner

@AntoninMarchardDev commented on GitHub (Nov 22, 2025):

@dosu

The call to /expo-authorization-proxy return a 404 not found
So it means this route do not exists on my API ? so do it mean that the package @thallesp/nestjs-better-auth is not complete ?

@AntoninMarchardDev commented on GitHub (Nov 22, 2025): @dosu The call to /expo-authorization-proxy return a 404 not found So it means this route do not exists on my API ? so do it mean that the package @thallesp/nestjs-better-auth is not complete ?
Author
Owner

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

Yes, the 404 means the /expo-authorization-proxy route does not exist on your API. This endpoint is required for Expo OAuth flows and is implemented in the official Better Auth Expo plugin, but the community package @thallesp/nestjs-better-auth does not provide it or the related proxy logic. This is a known limitation of the community package, not a misconfiguration on your part—so Expo social login cannot work fully until this endpoint is present.

To resolve this, you have a few options: implement a compatible /expo-authorization-proxy endpoint in your NestJS backend (based on the official plugin’s logic), migrate to the official Better Auth backend with Expo plugin support, or contribute this feature to the community package. If you want to build the endpoint yourself, you’ll need to create a GET route that takes an authorizationURL query param, extracts the state, sets a signed state cookie, and redirects to the authorizationURL—mirroring the official implementation (reference).

Until native NestJS support lands in Better Auth, custom implementations like this are the only workaround for Expo OAuth flows in NestJS backends. More context and workarounds are discussed in this thread.

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 (Nov 22, 2025): <!-- Answer --> Yes, the 404 means the /expo-authorization-proxy route does not exist on your API. This endpoint is required for Expo OAuth flows and is implemented in the official Better Auth Expo plugin, but the community package @thallesp/nestjs-better-auth does not provide it or the related proxy logic. This is a known limitation of the community package, not a misconfiguration on your part—so Expo social login cannot work fully until this endpoint is present. To resolve this, you have a few options: implement a compatible /expo-authorization-proxy endpoint in your NestJS backend (based on the official plugin’s logic), migrate to the official Better Auth backend with Expo plugin support, or contribute this feature to the community package. If you want to build the endpoint yourself, you’ll need to create a GET route that takes an authorizationURL query param, extracts the state, sets a signed state cookie, and redirects to the authorizationURL—mirroring the official implementation ([reference](https://github.com/better-auth/better-auth/blob/f922cf2320160f01bc3e75577fbdd04258aef528/packages/expo/src/index.ts#L30-L119)). Until native NestJS support lands in Better Auth, custom implementations like this are the only workaround for Expo OAuth flows in NestJS backends. More context and workarounds are discussed in [this thread](https://github.com/better-auth/better-auth/issues/359#issuecomment-2454853665). <!-- 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/4a453c88-5179-4867-a899-4405e91b06bd?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/4a453c88-5179-4867-a899-4405e91b06bd?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/4a453c88-5179-4867-a899-4405e91b06bd?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/4a453c88-5179-4867-a899-4405e91b06bd?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/4a453c88-5179-4867-a899-4405e91b06bd?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/4a453c88-5179-4867-a899-4405e91b06bd?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/4a453c88-5179-4867-a899-4405e91b06bd?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=Yes%2C%20the%20404%20means%20the%20/expo-authorization-proxy%20route%20does%20not%20exist%20on%20your%20API.%20This%20endpoint%20is%20required%20for%20Expo%20OAuth%20flows%20and%20is%20implemented%20in%20the%20official%20Better%20Auth%20Expo%20plugin%2C%20but%20the%20community%20package%20%40thallesp/nestjs-better-auth%20does%20not%20provide%20it%20or%20the%20related%20proxy%20logic.%20This%20is%20a%20known%20limitation%20of%20the%20community%20package%2C%20not%20a%20misconfiguration%20on%20your%20part%E2%80%94so%20Expo%20social%20login%20cannot%20work%20fully%20until%20this%20endpoint%20is%20present.%0A%0ATo%20resolve%20this%2C%20you%20have%20a%20few%20options%3A%20implement%20a%20compatible%20/expo-authorization-proxy%20endpoint%20in%20your%20NestJS%20backend%20%28based%20on%20the%20official%20plugin%E2%80%99s%20logic%29%2C%20migrate%20to%20the%20official%20Better%20Auth%20backend%20with%20Expo%20plugin%20support%2C%20or%20contribute%20this%20feature%20to%20the%20community%20package.%20If%20you%20want%20to%20build%20the%20endpoint%20yourself%2C%20you%E2%80%99ll%20need%20to%20create%20a%20GET%20route%20that%20takes%20an%20authorizationURL%20query%20param%2C%20extracts%20the%20state%2C%20sets%20a%20signed%20state%20cookie%2C%20and%20redirects%20to%20the%20authorizationURL%E2%80%94mirroring%20the%20official%20implementation%20%28%5Breference%5D%28https%3A//github.com/better-auth/better-auth/blob/f922cf2320160f01bc3e75577fbdd04258aef528/packages/expo/src/index.ts%23L30-L119%29%29.%20%0A%0AUntil%20native%20NestJS%20support%20lands%20in%20Better%20Auth%2C%20custom%20implementations%20like%20this%20are%20the%20only%20workaround%20for%20Expo%20OAuth%20flows%20in%20NestJS%20backends.%20More%20context%20and%20workarounds%20are%20discussed%20in%20%5Bthis%20thread%5D%28https%3A//github.com/better-auth/better-auth/issues/359%23issuecomment-2454853665%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/6207)
Author
Owner

@danielfinke commented on GitHub (Nov 29, 2025):

The call to /expo-authorization-proxy return a 404 not found So it means this route do not exists on my API ? so do it mean that the package @thallesp/nestjs-better-auth is not complete ?

@AntoninMarchardDev You just need to add the Expo plugin on the server-side I believe. I just went through this the other day myself. See: https://www.better-auth.com/docs/integrations/expo#add-the-expo-plugin-on-your-server

@danielfinke commented on GitHub (Nov 29, 2025): > The call to /expo-authorization-proxy return a 404 not found So it means this route do not exists on my API ? so do it mean that the package @thallesp/nestjs-better-auth is not complete ? @AntoninMarchardDev You just need to add the Expo plugin on the server-side I believe. I just went through this the other day myself. See: https://www.better-auth.com/docs/integrations/expo#add-the-expo-plugin-on-your-server
Author
Owner

@himself65 commented on GitHub (Dec 2, 2025):

Fixed in the latest version

@himself65 commented on GitHub (Dec 2, 2025): Fixed in the latest version
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#2360