[GH-ISSUE #5892] OAuth state_mismatch error persists on iPhone Chrome browser despite cookie configuration #10371

Closed
opened 2026-04-13 06:28:21 -05:00 by GiteaMirror · 2 comments
Owner

Originally created by @haxurn on GitHub (Nov 11, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/5892

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Set up Better Auth with OAuth provider (Google/GitHub/Apple)
  2. Configure state cookie with SameSite=None and Secure=true as documented
  3. Test OAuth flow on desktop browser (works correctly)
  4. Test same OAuth flow on iPhone Chrome browser
  5. OAuth redirects to provider successfully
  6. Provider redirects back to callback URL
  7. state_mismatch error occurs on iPhone, redirected to /error?error=state_mismatch

Current vs. Expected behavior

Current: OAuth flow fails with state_mismatch error specifically on iPhone Chrome browser, despite applying the documented cookie configuration fixes from issues #5243 and related discussions.
Expected: OAuth flow should work consistently across all browsers, including iPhone Chrome, when proper cookie configuration is applied.

What version of Better Auth are you using?

1.3.18

System info

{
  "platform": "iOS",
  "browser": "Chrome on iPhone", 
  "framework": "[Backend: Hono, Frontend: Nextjs]",
  "deployment": "Windows"
}

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

Backend, Client, Package

Auth config (if applicable)

import { db } from "@/db";
import * as schema from "@/db/schema/auth";
import type { BetterAuthOptions } from "better-auth";
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import {
	admin,
	lastLoginMethod,
	openAPI,
	organization,
	phoneNumber,
	username,
} from "better-auth/plugins";

const authConfig: BetterAuthOptions = {
	database: drizzleAdapter(db, {
		provider: "pg",
		schema,
	}),
	baseURL: process.env.BETTER_AUTH_URL || "http://localhost:8000",
	secret: process.env.BETTER_AUTH_SECRET!,
	session: {
		expiresIn: 60 * 60 * 24 * 7, // 7 days
		updateAge: 60 * 60 * 24, // 1 day
		cookieCache: {
			enabled: false,
		},
	},
	account: {
		accountLinking: {
			enabled: true,
			allowDifferentEmails: true,
			trustedProviders: ["google", "github"],
		},
	},

	plugins: [
		username(),
		admin({
			adminUserIds: [process.env.ADMIN_USER_ID! as string],
		}),
		organization(),
		phoneNumber(),
		lastLoginMethod(),
		openAPI(),
	],

	socialProviders: {
		google: {
			clientId: process.env.GOOGLE_CLIENT_ID!,
			clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
		},
		github: {
			clientId: process.env.GITHUB_CLIENT_ID!,
			clientSecret: process.env.GITHUB_CLIENT_SECRET!,
		},
	},

	trustedOrigins: [
		"http://localhost:3000",
		"http://localhost:8000",
		"http://127.0.0.1:3000",
		"https://accounts.google.com",
		...(process.env.CORS_ORIGIN && process.env.CORS_ORIGIN !== "*"
			? process.env.CORS_ORIGIN.split(",")
			: []),
	],

	user: {
		deleteUser: {
			enabled: true,
		},
		additionalFields: {
			preferences: {
				type: "json",
				required: false,
			},
			bio: {
				type: "string",
				required: false,
			},
			location: {
				type: "string",
				required: false,
			},
		},
	},

	advanced: {
		cookies: {
			state: {
				attributes: {
					sameSite: "none",
					secure: true,
				}
			}
		},
		useSecureCookies: true,
		crossSubDomainCookies: {
			enabled: false,
		},
		defaultCookieAttributes: {
			sameSite: "none",
			secure: true,
			domain: undefined,
			path: "/",
		},
		cookiePrefix: "better_auth",
		disableCSRFCheck: true,
	},
};

export const auth = betterAuth(authConfig) as ReturnType<typeof betterAuth>;

Additional context

Desktop browsers work fine - OAuth flow completes successfully on Chrome/Safari/Firefox on desktop
iPhone-specific issue - Same flow fails consistently on iPhone Chrome (and likely Safari)
Applied documented fixes - Tried both targeted state cookie config and global cookie config from issues #5243 and #5519
Client-side initiation - Confirmed OAuth is initiated from client-side, not server-side
Proper redirect URIs - OAuth provider redirect URI points to API callback, client callbackURL points to user route
Version regression - This appears related to changes after v1.3.18 as mentioned in discussion #5519
Debugging observations
OAuth state cookie appears to be set correctly in browser dev tools
The issue seems specific to how iPhone browsers handle the state validation during callback
Error consistently shows state_mismatch rather than missing state entirely
Same configuration works perfectly on desktop browsers
This appears to be an iPhone-specific browser compatibility issue that persists despite applying all documented workarounds for state_mismatch errors.

Originally created by @haxurn on GitHub (Nov 11, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/5892 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Set up Better Auth with OAuth provider (Google/GitHub/Apple) 2. Configure state cookie with `SameSite=None` and `Secure=true` as documented 3. Test OAuth flow on desktop browser (works correctly) 4. Test same OAuth flow on iPhone Chrome browser 5. OAuth redirects to provider successfully 6. Provider redirects back to callback URL 7. `state_mismatch` error occurs on iPhone, redirected to `/error?error=state_mismatch` ### Current vs. Expected behavior **Current:** OAuth flow fails with `state_mismatch` error specifically on iPhone Chrome browser, despite applying the documented cookie configuration fixes from issues #5243 and related discussions. **Expected:** OAuth flow should work consistently across all browsers, including iPhone Chrome, when proper cookie configuration is applied. ### What version of Better Auth are you using? 1.3.18 ### System info ```bash { "platform": "iOS", "browser": "Chrome on iPhone", "framework": "[Backend: Hono, Frontend: Nextjs]", "deployment": "Windows" } ``` ### Which area(s) are affected? (Select all that apply) Backend, Client, Package ### Auth config (if applicable) ```typescript import { db } from "@/db"; import * as schema from "@/db/schema/auth"; import type { BetterAuthOptions } from "better-auth"; import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { admin, lastLoginMethod, openAPI, organization, phoneNumber, username, } from "better-auth/plugins"; const authConfig: BetterAuthOptions = { database: drizzleAdapter(db, { provider: "pg", schema, }), baseURL: process.env.BETTER_AUTH_URL || "http://localhost:8000", secret: process.env.BETTER_AUTH_SECRET!, session: { expiresIn: 60 * 60 * 24 * 7, // 7 days updateAge: 60 * 60 * 24, // 1 day cookieCache: { enabled: false, }, }, account: { accountLinking: { enabled: true, allowDifferentEmails: true, trustedProviders: ["google", "github"], }, }, plugins: [ username(), admin({ adminUserIds: [process.env.ADMIN_USER_ID! as string], }), organization(), phoneNumber(), lastLoginMethod(), openAPI(), ], socialProviders: { google: { clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, }, github: { clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET!, }, }, trustedOrigins: [ "http://localhost:3000", "http://localhost:8000", "http://127.0.0.1:3000", "https://accounts.google.com", ...(process.env.CORS_ORIGIN && process.env.CORS_ORIGIN !== "*" ? process.env.CORS_ORIGIN.split(",") : []), ], user: { deleteUser: { enabled: true, }, additionalFields: { preferences: { type: "json", required: false, }, bio: { type: "string", required: false, }, location: { type: "string", required: false, }, }, }, advanced: { cookies: { state: { attributes: { sameSite: "none", secure: true, } } }, useSecureCookies: true, crossSubDomainCookies: { enabled: false, }, defaultCookieAttributes: { sameSite: "none", secure: true, domain: undefined, path: "/", }, cookiePrefix: "better_auth", disableCSRFCheck: true, }, }; export const auth = betterAuth(authConfig) as ReturnType<typeof betterAuth>; ``` ### Additional context Desktop browsers work fine - OAuth flow completes successfully on Chrome/Safari/Firefox on desktop iPhone-specific issue - Same flow fails consistently on iPhone Chrome (and likely Safari) Applied documented fixes - Tried both targeted state cookie config and global cookie config from issues #5243 and #5519 Client-side initiation - Confirmed OAuth is initiated from client-side, not server-side Proper redirect URIs - OAuth provider redirect URI points to API callback, client callbackURL points to user route Version regression - This appears related to changes after v1.3.18 as mentioned in discussion #5519 Debugging observations OAuth state cookie appears to be set correctly in browser dev tools The issue seems specific to how iPhone browsers handle the state validation during callback Error consistently shows state_mismatch rather than missing state entirely Same configuration works perfectly on desktop browsers This appears to be an iPhone-specific browser compatibility issue that persists despite applying all documented workarounds for state_mismatch errors.
GiteaMirror added the locked label 2026-04-13 06:28:21 -05:00
Author
Owner

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

Hello, thanks for bringing this up! Based on your description and troubleshooting steps already taken, it appears that iOS browsers (even in Chrome, which shares underlying behavior with Safari) may impose stricter rules on cookies—even when correctly set to SameSite=None and Secure. This state mismatch likely means that the state cookie isn’t being sent back during the OAuth callback due to these browser-specific limitations.

A few next steps you might consider:

• Verify whether the cookie domain and path settings are fully aligned with the redirect and callback URLs. In some cases, explicitly defining the cookie’s domain can help overcome differences in how iOS handles cookies during redirects [1].

• Check if any third-party cookie restrictions or tracking prevention settings on iOS are interfering with the state cookie’s transmission. iOS browsers might block cookies that are cross-site or set in a way that triggers privacy limitations.

• Consider instrumenting additional logging to confirm that, on iPhone Chrome, the state cookie is indeed present in the initial OAuth request and being sent on callback. This can help confirm if the cookie is altered or dropped during the browser’s redirection process.

• As a workaround, if possible, evaluate if a different state management strategy (such as storing state in session storage for the duration of the flow) can serve as a temporary solution on iOS platforms.

These steps are in line with some of our previous discussions on similar issues. If these recommendations don’t solve the problem, I’d suggest tagging @bekacru and @ping__ so they can lend further insight.

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

What additional details can you share about the redirection behavior in iOS, or have you noticed any other irregularities in the cookies when inspected on the iPhone?

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3514474289 --> @better-auth-agent[bot] commented on GitHub (Nov 11, 2025): Hello, thanks for bringing this up! Based on your description and troubleshooting steps already taken, it appears that iOS browsers (even in Chrome, which shares underlying behavior with Safari) may impose stricter rules on cookies—even when correctly set to SameSite=None and Secure. This state mismatch likely means that the state cookie isn’t being sent back during the OAuth callback due to these browser-specific limitations. A few next steps you might consider: • Verify whether the cookie domain and path settings are fully aligned with the redirect and callback URLs. In some cases, explicitly defining the cookie’s domain can help overcome differences in how iOS handles cookies during redirects [[1]](https://github.com/better-auth/better-auth/issues/4125). • Check if any third-party cookie restrictions or tracking prevention settings on iOS are interfering with the state cookie’s transmission. iOS browsers might block cookies that are cross-site or set in a way that triggers privacy limitations. • Consider instrumenting additional logging to confirm that, on iPhone Chrome, the state cookie is indeed present in the initial OAuth request and being sent on callback. This can help confirm if the cookie is altered or dropped during the browser’s redirection process. • As a workaround, if possible, evaluate if a different state management strategy (such as storing state in session storage for the duration of the flow) can serve as a temporary solution on iOS platforms. These steps are in line with some of our previous discussions on similar issues. If these recommendations don’t solve the problem, I’d suggest tagging @bekacru and @ping__ so they can lend further insight. tag @better-auth-agent if you would like an answer from the Agent What additional details can you share about the redirection behavior in iOS, or have you noticed any other irregularities in the cookies when inspected on the iPhone? <!-- 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

@himself65 commented on GitHub (Nov 13, 2025):

I think the issue you mentioned has a different reason. For your case, I think it violates the iOS system cookie behavior. You cannot set sameSite: "none"

I'm closing as this is not our side issue

Refs: https://news.ycombinator.com/item?id=41599621

<!-- gh-comment-id:3529771465 --> @himself65 commented on GitHub (Nov 13, 2025): I think the issue you mentioned has a different reason. For your case, I think it violates the iOS system cookie behavior. You cannot set `sameSite: "none"` I'm closing as this is not our side issue Refs: https://news.ycombinator.com/item?id=41599621
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#10371