[GH-ISSUE #2493] Corrupted/malformed headers with Expo + Next.js break session retrieval #9224

Closed
opened 2026-04-13 04:38:16 -05:00 by GiteaMirror · 17 comments
Owner

Originally created by @subvertallchris on GitHub (Apr 30, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/2493

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Find a full reproduction repo with instructions at https://github.com/subvertallchris/BetterAuthExpoNextBroken/

Current vs. Expected behavior

Current: when using Expo + Next.js plugins, it appears that refresh behavior around cookieCache is getting a new better_auth.session_data value that it holds in memory and seemingly combines with fetch (!?) preventing serverside session verification.

Expected: it should not do this.

What version of Better Auth are you using?

1.27

Provide environment information

- Mac 14.3.1
- Expo 52.0.46
- iOS simulator 15.2 running iOS 17.2

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

Backend, Client

Auth config (if applicable)

You'll find it in the reproduction.

Additional context

We are using BetterAuth on the web and it is flawless, this problem does not exist anywhere other than iOS. We have not tested this with Android or physical device yet.

Originally created by @subvertallchris on GitHub (Apr 30, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/2493 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce Find a full reproduction repo with instructions at https://github.com/subvertallchris/BetterAuthExpoNextBroken/ ### Current vs. Expected behavior Current: when using Expo + Next.js plugins, it appears that refresh behavior around `cookieCache` is getting a new `better_auth.session_data` value that it holds in memory and seemingly combines with `fetch` (!?) preventing serverside session verification. Expected: it should not do this. ### What version of Better Auth are you using? 1.27 ### Provide environment information ```bash - Mac 14.3.1 - Expo 52.0.46 - iOS simulator 15.2 running iOS 17.2 ``` ### Which area(s) are affected? (Select all that apply) Backend, Client ### Auth config (if applicable) ```typescript You'll find it in the reproduction. ``` ### Additional context We are using BetterAuth on the web and it is flawless, this problem does not exist anywhere other than iOS. We have not tested this with Android or physical device yet.
GiteaMirror added the locked label 2026-04-13 04:38:16 -05:00
Author
Owner

@subvertallchris commented on GitHub (Apr 30, 2025):

There is a good chance this is because credentials: "omit" is ignored by iOS native modules. Apparently, iOS might store cookies on the native side and include them. Clearing native cookies might help.

import RCTNetworking from 'react-native/Libraries/Network/RCTNetworking.ios';

function clearCookies() {
  RCTNetworking.clearCookies((cleared) => {
    console.log('Cookies cleared, had cookies=' + cleared.toString());
  });
}

Calling that prior to every request might do the trick...? Would love guidance here.

UPDATE: calling this before every request does seem to work around it but I want to test it more. This exemplifies why cookie sessions are not recommended with iOS and why JWTs are preferable. If this is correct then the current implementation is not suitable for RN.

<!-- gh-comment-id:2842989036 --> @subvertallchris commented on GitHub (Apr 30, 2025): There is a good chance this is because `credentials: "omit"` is ignored by iOS native modules. Apparently, iOS might store cookies on the native side and include them. Clearing native cookies might help. ``` import RCTNetworking from 'react-native/Libraries/Network/RCTNetworking.ios'; function clearCookies() { RCTNetworking.clearCookies((cleared) => { console.log('Cookies cleared, had cookies=' + cleared.toString()); }); } ``` Calling that prior to every request might do the trick...? Would love guidance here. UPDATE: calling this before every request does seem to work around it but I want to test it more. This exemplifies why cookie sessions are not recommended with iOS and why JWTs are preferable. If this is correct then the current implementation is not suitable for RN.
Author
Owner

@asobirov commented on GitHub (May 1, 2025):

Hey! You might want to check out https://github.com/asobirov/t3-turbo-better-auth - this is what I ended up putting together to use Better Auth with Next.js and Expo. Not entirely sure what the issue is in your case, but hopefully this helps in some way

<!-- gh-comment-id:2844966635 --> @asobirov commented on GitHub (May 1, 2025): Hey! You might want to check out https://github.com/asobirov/t3-turbo-better-auth - this is what I ended up putting together to use Better Auth with Next.js and Expo. Not entirely sure what the issue is in your case, but hopefully this helps in some way
Author
Owner

@subvertallchris commented on GitHub (May 2, 2025):

Hey @asobirov, this looks good and I'm curious about how you got Expo working with pnpm, but this looks like it will be susceptible to this same iOS problem. Perhaps tRPC is modifying fetch in some way to get better control of the requests.

Have you used this with the iOS Simulator much? The React Native docs remark and link to GitHub issues where they discuss how cookie-based authentication is not advised, https://reactnative.dev/docs/network#known-issues-with-fetch-and-cookie-based-authentication. As far as I can tell, Better Auth's Expo plugin and current approach is inherently unstable on iOS. We should figure out something that uses a different header, possibly just a JWT approach.

<!-- gh-comment-id:2847843298 --> @subvertallchris commented on GitHub (May 2, 2025): Hey @asobirov, this looks good and I'm curious about how you got Expo working with pnpm, but this looks like it will be susceptible to this same iOS problem. Perhaps tRPC is modifying fetch in some way to get better control of the requests. Have you used this with the iOS Simulator much? The React Native docs remark and link to GitHub issues where they discuss how cookie-based authentication is not advised, https://reactnative.dev/docs/network#known-issues-with-fetch-and-cookie-based-authentication. As far as I can tell, Better Auth's Expo plugin and current approach is inherently unstable on iOS. We should figure out something that uses a different header, possibly just a JWT approach.
Author
Owner

@quuentinho commented on GitHub (Jun 11, 2025):

Any updates on this issue ?

<!-- gh-comment-id:2961638098 --> @quuentinho commented on GitHub (Jun 11, 2025): Any updates on this issue ?
Author
Owner

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

Hi, @subvertallchris. I'm Dosu, and I'm helping the better-auth team manage their backlog and am marking this issue as stale.

Issue Summary:

  • You reported that Better Auth 1.27 used with Expo and Next.js on iOS causes corrupted headers and breaks server-side session retrieval due to problematic cookie handling.
  • The issue does not occur on the web, and you suspect iOS native modules ignore credentials: "omit".
  • Clearing native cookies via RCTNetworking may temporarily work around the problem, but this highlights why cookie sessions are not recommended on iOS.
  • You suggested that JWTs might be a better approach for session management on iOS.
  • Related discussions and shared repos have not yet resolved the issue.

What's Next:

  • Please let me know if this issue is still relevant with the latest version of better-auth by commenting here to keep the discussion open.
  • If I do not hear back within 7 days, this issue will be automatically closed.

Thank you for your understanding and contribution!

<!-- gh-comment-id:3275593283 --> @dosubot[bot] commented on GitHub (Sep 10, 2025): Hi, @subvertallchris. I'm [Dosu](https://dosu.dev), and I'm helping the better-auth team manage their backlog and am marking this issue as stale. **Issue Summary:** - You reported that Better Auth 1.27 used with Expo and Next.js on iOS causes corrupted headers and breaks server-side session retrieval due to problematic cookie handling. - The issue does not occur on the web, and you suspect iOS native modules ignore `credentials: "omit"`. - Clearing native cookies via RCTNetworking may temporarily work around the problem, but this highlights why cookie sessions are not recommended on iOS. - You suggested that JWTs might be a better approach for session management on iOS. - Related discussions and shared repos have not yet resolved the issue. **What's Next:** - Please let me know if this issue is still relevant with the latest version of better-auth by commenting here to keep the discussion open. - If I do not hear back within 7 days, this issue will be automatically closed. Thank you for your understanding and contribution!
Author
Owner

@subvertallchris commented on GitHub (Sep 10, 2025):

I haven't seen any indication that this has been resolved so it should stay open.

<!-- gh-comment-id:3275604947 --> @subvertallchris commented on GitHub (Sep 10, 2025): I haven't seen any indication that this has been resolved so it should stay open.
Author
Owner

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

Hi, @subvertallchris. I'm Dosu, and I'm helping the better-auth team manage their backlog and am marking this issue as stale.

Issue Summary:

  • You reported that BetterAuth 1.27 used with Expo and Next.js on iOS causes corrupted headers and breaks server-side session retrieval.
  • The root cause appears to be iOS ignoring credentials: "omit" and issues with native cookie handling.
  • You found that clearing native cookies via RCTNetworking can temporarily mitigate the problem.
  • You suggested that cookie-based sessions are unstable on iOS and recommended JWTs as a more reliable alternative.
  • The issue remains unresolved, and you have confirmed it is still relevant.

Next Steps:

  • Please confirm if this issue persists with the latest version of better-auth and if you have any new insights or workarounds.
  • If this issue is still relevant, you can keep the discussion open by commenting here; otherwise, I will automatically close the issue in 7 days.

Thank you for your understanding and contribution!

<!-- gh-comment-id:3637856644 --> @dosubot[bot] commented on GitHub (Dec 10, 2025): Hi, @subvertallchris. I'm [Dosu](https://dosu.dev), and I'm helping the better-auth team manage their backlog and am marking this issue as stale. **Issue Summary:** - You reported that BetterAuth 1.27 used with Expo and Next.js on iOS causes corrupted headers and breaks server-side session retrieval. - The root cause appears to be iOS ignoring `credentials: "omit"` and issues with native cookie handling. - You found that clearing native cookies via RCTNetworking can temporarily mitigate the problem. - You suggested that cookie-based sessions are unstable on iOS and recommended JWTs as a more reliable alternative. - The issue remains unresolved, and you have confirmed it is still relevant. **Next Steps:** - Please confirm if this issue persists with the latest version of better-auth and if you have any new insights or workarounds. - If this issue is still relevant, you can keep the discussion open by commenting here; otherwise, I will automatically close the issue in 7 days. Thank you for your understanding and contribution!
Author
Owner

@subvertallchris commented on GitHub (Dec 10, 2025):

Has anyone else checked this?

<!-- gh-comment-id:3637930180 --> @subvertallchris commented on GitHub (Dec 10, 2025): Has anyone else checked this?
Author
Owner

@bytaesu commented on GitHub (Feb 1, 2026):

Hi @subvertallchris,

Could you let me know if this also happens with the latest versions of Better Auth and Next.js? If so, I'd like to fix it 🙂

<!-- gh-comment-id:3830081880 --> @bytaesu commented on GitHub (Feb 1, 2026): Hi @subvertallchris, Could you let me know if this also happens with the latest versions of Better Auth and Next.js? If so, I'd like to fix it 🙂
Author
Owner

@subvertallchris commented on GitHub (Feb 1, 2026):

Thanks @bytaesu! I’ll disable my workaround on Monday and report back. 🫡

<!-- gh-comment-id:3830334612 --> @subvertallchris commented on GitHub (Feb 1, 2026): Thanks @bytaesu! I’ll disable my workaround on Monday and report back. 🫡
Author
Owner

@subvertallchris commented on GitHub (Feb 7, 2026):

Hey @bytaesu, sorry for delay here, last week was a wild one. This is still on my list esp as we are trying to ship the mobile app to prod very soon. Hoping to get it under the microscope this week.

<!-- gh-comment-id:3865204939 --> @subvertallchris commented on GitHub (Feb 7, 2026): Hey @bytaesu, sorry for delay here, last week was a wild one. This is still on my list esp as we are trying to ship the mobile app to prod very soon. Hoping to get it under the microscope this week.
Author
Owner

@subvertallchris commented on GitHub (Feb 17, 2026):

Hi @bytaesu, jumping back into this today. I haven't tested in the code but right off the bat:

This looks inherently incompatible. My workaround is working but diving into the iOS internals is deprecated so we're on borrowed time. I'm currently working on adding JWT, which I expect to be stable. As far as I can tell, the Expo plugin just needs to get away from treating its token as a Cookie and move to something supported by React Native.

<!-- gh-comment-id:3917417264 --> @subvertallchris commented on GitHub (Feb 17, 2026): Hi @bytaesu, jumping back into this today. I haven't tested in the code but right off the bat: * React Native's docs still say that `credentials: "omit"` does not work with fetch - https://reactnative.dev/docs/network#known-issues-with-fetch-and-cookie-based-authentication * Better Auth docs still say to use `credentials: "omit"` This looks inherently incompatible. My workaround is working but diving into the iOS internals is deprecated so we're on borrowed time. I'm currently working on adding JWT, which I expect to be stable. As far as I can tell, the Expo plugin just needs to get away from treating its token as a Cookie and move to something supported by React Native.
Author
Owner

@bytaesu commented on GitHub (Feb 18, 2026):

@subvertallchris Thanks for letting us know. I'll check today 🧐

<!-- gh-comment-id:3918954175 --> @bytaesu commented on GitHub (Feb 18, 2026): @subvertallchris Thanks for letting us know. I'll check today 🧐
Author
Owner

@subvertallchris commented on GitHub (Feb 18, 2026):

Bearer tokens are working mostly ok. BetterAuth seems to have a very cookie-centric perspective, so I've found a few spots where empty cookie strings are sneaking into requests and causing unexpected results. I'm not sure if this caused by the Expo plugin or the BetterAuth client, but it's been more challenging to resolve than I expected. I suggest moving the Expo plugin entirely to Bearer for the Expo client. Easy wins: store, retrieve, pass along into queries. Part of the challenge here is that each mobile platform handles attempts to set cookies differently, Android seems to work, iOS works sometimes and with hacks/workarounds, so it'd be best to avoid it.

One of the strange issues: calling authClient.logout() when using bearer would 400 the first time because the request was inserting an empty string of BetterAuth cookies and ignoring the Authorization header. Something about this first request would then set the missing headers within the mobile app, so calling logout() again immediately would succeed and properly logout. 🙃

Happy to provide more notes, troubleshoot, etc,... if it's helpful.

<!-- gh-comment-id:3923701935 --> @subvertallchris commented on GitHub (Feb 18, 2026): Bearer tokens are working mostly ok. BetterAuth seems to have a very cookie-centric perspective, so I've found a few spots where empty cookie strings are sneaking into requests and causing unexpected results. I'm not sure if this caused by the Expo plugin or the BetterAuth client, but it's been more challenging to resolve than I expected. I suggest moving the Expo plugin entirely to Bearer for the Expo client. Easy wins: store, retrieve, pass along into queries. Part of the challenge here is that each mobile platform handles attempts to set cookies differently, Android seems to work, iOS works sometimes and with hacks/workarounds, so it'd be best to avoid it. One of the strange issues: calling `authClient.logout()` when using bearer would 400 the first time because the request was inserting an empty string of BetterAuth cookies and ignoring the Authorization header. Something about this first request would then set the missing headers within the mobile app, so calling `logout()` again immediately would succeed and properly logout. 🙃 Happy to provide more notes, troubleshoot, etc,... if it's helpful.
Author
Owner

@bytaesu commented on GitHub (Feb 21, 2026):

Hi @subvertallchris,


1]

I noticed an issue where expired cookies were not being handled properly and have applied a fix -> #8090


2]

I also tested credentials: "omit", and the issue could not be reproduced in a Cloudflare Workers + Expo iOS simulator environment.

// Hit /debug/set-cookie with credentials:"include"
// → server responds with Set-Cookie → iOS stores the cookie natively
const setupRes = await fetch(`${BETTER_AUTH_URL}/debug/set-cookie`, {
  credentials: "include",
});
const setupData = await setupRes.json();

// Verify the native cookie was stored by fetching with credentials:"include"
const includeRes = await fetch(`${BETTER_AUTH_URL}/debug/headers`, {
  credentials: "include",
  headers: { "x-test": "credentials-include" },
});
const includeData = await includeRes.json();
const nativeCookieStored =
  includeData.cookie !== "(none)" && includeData.cookie.includes("debug_test");

// Now test credentials:"omit" — iOS should NOT send the stored cookie
const omitRes = await fetch(`${BETTER_AUTH_URL}/debug/headers`, {
  credentials: "omit",
  headers: { "x-test": "credentials-omit" },
});
const omitData = await omitRes.json();
const omitLeakedCookie =
  omitData.cookie !== "(none)" && omitData.cookie.includes("debug_test");
Image

3]

Additionally, the initial error returned by signOut may have been caused by an already expired cookie. With the cookie handling fix in place, this issue should now be fully resolved.

[wrangler:info] GET /auth/get-session 200 OK (5ms)
[wrangler:info] POST /auth/sign-out 200 OK (1220ms)
[wrangler:info] GET /auth/get-session 200 OK (2ms)
[wrangler:info] POST /auth/sign-in/email 200 OK (1197ms)
[wrangler:info] GET /auth/get-session 200 OK (5ms)
[wrangler:info] POST /auth/sign-out 200 OK (1071ms)
[wrangler:info] GET /auth/get-session 200 OK (3ms)

I’ll close this issue with the above PR. If the problem persists, please feel free to let me know anytime. Thanks for letting us know 🫡

<!-- gh-comment-id:3938932877 --> @bytaesu commented on GitHub (Feb 21, 2026): Hi @subvertallchris, --- 1] I noticed an issue where expired cookies were not being handled properly and have applied a fix -> #8090 --- 2] I also tested credentials: "omit", and the issue could not be reproduced in a Cloudflare Workers + Expo iOS simulator environment. ```ts // Hit /debug/set-cookie with credentials:"include" // → server responds with Set-Cookie → iOS stores the cookie natively const setupRes = await fetch(`${BETTER_AUTH_URL}/debug/set-cookie`, { credentials: "include", }); const setupData = await setupRes.json(); // Verify the native cookie was stored by fetching with credentials:"include" const includeRes = await fetch(`${BETTER_AUTH_URL}/debug/headers`, { credentials: "include", headers: { "x-test": "credentials-include" }, }); const includeData = await includeRes.json(); const nativeCookieStored = includeData.cookie !== "(none)" && includeData.cookie.includes("debug_test"); // Now test credentials:"omit" — iOS should NOT send the stored cookie const omitRes = await fetch(`${BETTER_AUTH_URL}/debug/headers`, { credentials: "omit", headers: { "x-test": "credentials-omit" }, }); const omitData = await omitRes.json(); const omitLeakedCookie = omitData.cookie !== "(none)" && omitData.cookie.includes("debug_test"); ``` <img width="385" height="299" alt="Image" src="https://github.com/user-attachments/assets/9287cd6f-bf6f-49fe-9fd6-07b9a8572986" /> --- 3] Additionally, the initial error returned by signOut may have been caused by an already expired cookie. With the cookie handling fix in place, this issue should now be fully resolved. ``` [wrangler:info] GET /auth/get-session 200 OK (5ms) [wrangler:info] POST /auth/sign-out 200 OK (1220ms) [wrangler:info] GET /auth/get-session 200 OK (2ms) [wrangler:info] POST /auth/sign-in/email 200 OK (1197ms) [wrangler:info] GET /auth/get-session 200 OK (5ms) [wrangler:info] POST /auth/sign-out 200 OK (1071ms) [wrangler:info] GET /auth/get-session 200 OK (3ms) ``` --- I’ll close this issue with the above PR. If the problem persists, please feel free to let me know anytime. Thanks for letting us know 🫡
Author
Owner

@subvertallchris commented on GitHub (Feb 21, 2026):

I’ll give it a shot, thank you. What are your thoughts on the React Native docs saying cookie auth is unstable?

<!-- gh-comment-id:3939072690 --> @subvertallchris commented on GitHub (Feb 21, 2026): I’ll give it a shot, thank you. What are your thoughts on the React Native docs saying cookie auth is unstable?
Author
Owner

@bytaesu commented on GitHub (Feb 21, 2026):

@subvertallchris

@better-auth/expo doesn’t use cookies directly. It stores and manages them in SecureStore, then attaches them to requests. So it may not be related to the native cookie storage 🤔

<!-- gh-comment-id:3939215796 --> @bytaesu commented on GitHub (Feb 21, 2026): @subvertallchris `@better-auth/expo` doesn’t use cookies directly. It stores and manages them in **SecureStore**, then attaches them to requests. So it may not be related to the native cookie storage 🤔
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#9224