[GH-ISSUE #7014] better-auth expo plugin expo-origin header bypass takes no effect because of setting readonly request headers #28026

Closed
opened 2026-04-17 19:22:10 -05:00 by GiteaMirror · 13 comments
Owner

Originally created by @techzealot on GitHub (Dec 27, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/7014

Originally assigned to: @bytaesu on GitHub.

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. use regular better-auth expo config and use emailAndPassword plugin
  2. config your trustedOrigins such as "my-app://"
  3. call signIn or any other api
  4. MISSING_OR_NULL_ORIGIN error occurred

Current vs. Expected behavior

current:
expo-origin header override failed to add origin, thus cause MISSING_OR_NULL_ORIGIN error

expected:
the expo plugin add correct origin to avoid origin check error

What version of Better Auth are you using?

1.4.7

System info

1. macos
2. node v23.10.0
3. better-auth 1.4.6
4. expo 53.0.24

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

Package

Auth config (if applicable)

const options = {
  experimental: {
    joins: true
  },
  database: drizzleAdapter(db, {
    provider: "pg"
  }),
  
  baseURL: CONFIG.BETTER_AUTH_URL,

  emailAndPassword: {
    enabled: true,
    requireEmailVerification: false,
  },
  plugins: [
    expo(),
    admin(),
 ],

  trustedOrigins: [
    "http://localhost:3000",
    "http://localhost:8081", // Expo dev server
    "my-app://", // Expo app scheme
    "exp://", // Expo Go
  ]
} satisfies BetterAuthOptions;

Additional context

I think the header bypass logic caused the problem:

9c54c108db/packages/expo/src/index.ts (L26-L43)

the request.headers is readonly ,set takes no effect

I write a fix plugin to test the correct solution, it works:

export const expoOriginFix = (): BetterAuthPlugin => {
  return {
    id: "expo-origin-fix",
    onRequest: async (request) => {
     
      const expoOrigin = request.headers.get("expo-origin");

      if (!expoOrigin) {
       
        return;
      }

      // create new header because request.headers is readonly 
      const newHeaders = new Headers(request.headers);
      newHeaders.set("origin", expoOrigin)

      const newRequest = new Request(request, {
        headers: newHeaders
      });

      return {
        request: newRequest,
      };
    },
  };
};

Originally created by @techzealot on GitHub (Dec 27, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/7014 Originally assigned to: @bytaesu on GitHub. ### Is this suited for github? - [ ] Yes, this is suited for github ### To Reproduce 1. use regular better-auth expo config and use emailAndPassword plugin 2. config your trustedOrigins such as "my-app://" 3. call signIn or any other api 4. MISSING_OR_NULL_ORIGIN error occurred ### Current vs. Expected behavior current: expo-origin header override failed to add origin, thus cause MISSING_OR_NULL_ORIGIN error expected: the expo plugin add correct origin to avoid origin check error ### What version of Better Auth are you using? 1.4.7 ### System info ```bash 1. macos 2. node v23.10.0 3. better-auth 1.4.6 4. expo 53.0.24 ``` ### Which area(s) are affected? (Select all that apply) Package ### Auth config (if applicable) ```typescript const options = { experimental: { joins: true }, database: drizzleAdapter(db, { provider: "pg" }), baseURL: CONFIG.BETTER_AUTH_URL, emailAndPassword: { enabled: true, requireEmailVerification: false, }, plugins: [ expo(), admin(), ], trustedOrigins: [ "http://localhost:3000", "http://localhost:8081", // Expo dev server "my-app://", // Expo app scheme "exp://", // Expo Go ] } satisfies BetterAuthOptions; ``` ### Additional context I think the header bypass logic caused the problem: https://github.com/better-auth/better-auth/blob/9c54c108db80c5e51e824242416b3f2554331b10/packages/expo/src/index.ts#L26-L43 the request.headers is readonly ,set takes no effect I write a fix plugin to test the correct solution, it works: ``` export const expoOriginFix = (): BetterAuthPlugin => { return { id: "expo-origin-fix", onRequest: async (request) => { const expoOrigin = request.headers.get("expo-origin"); if (!expoOrigin) { return; } // create new header because request.headers is readonly const newHeaders = new Headers(request.headers); newHeaders.set("origin", expoOrigin) const newRequest = new Request(request, { headers: newHeaders }); return { request: newRequest, }; }, }; }; ```
GiteaMirror added the locked label 2026-04-17 19:22:10 -05:00
Author
Owner

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

It seems that you're encountering the issue where the expo-origin header bypass isn't effectively adding the origin, which leads to the MISSING_OR_NULL_ORIGIN error. This could be due to the readonly nature of request headers as you've mentioned, which can prevent modifications.

A related fix was attempted in a recent commit, which aimed at adding support for the expo-origin header in origin validation. This change ensures that if the Origin and Referer headers are missing, the expo-origin header will be accepted. Ideally, the order of preference is: Origin > Referer > expo-origin [1].

If you're still facing problems even with the fix, it might be useful to verify that you're using the latest version of the Better Auth packages. As you've already tested a custom plugin that works correctly, continuing to use that solution for the time being may be a good workaround.

If these steps don't resolve your issue, I recommend tagging @bekacru or @ping__ on GitHub. They can provide more personalized assistance!

What specific question do you have or what’s the next step you’d like to explore?

If you need more help, tag @better-auth-agent in a comment so I can respond.

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3693558587 --> @better-auth-agent[bot] commented on GitHub (Dec 27, 2025): It seems that you're encountering the issue where the expo-origin header bypass isn't effectively adding the origin, which leads to the MISSING_OR_NULL_ORIGIN error. This could be due to the readonly nature of request headers as you've mentioned, which can prevent modifications. A related fix was attempted in a recent commit, which aimed at adding support for the expo-origin header in origin validation. This change ensures that if the Origin and Referer headers are missing, the expo-origin header will be accepted. Ideally, the order of preference is: Origin > Referer > expo-origin [[1]](https://github.com/better-auth/better-auth/pull/6877). If you're still facing problems even with the fix, it might be useful to verify that you're using the latest version of the Better Auth packages. As you've already tested a custom plugin that works correctly, continuing to use that solution for the time being may be a good workaround. If these steps don't resolve your issue, I recommend tagging @bekacru or @ping__ on GitHub. They can provide more personalized assistance! What specific question do you have or what’s the next step you’d like to explore? _If you need more help, tag @better-auth-agent in a comment so I can respond._ <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) • [Discord](https://discord.gg/fG2XXEuQX3) • [GitHub](https://github.com/Skyvern-AI/Skyvern) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/fG2XXEuQX3) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/Skyvern-AI/Skyvern)
Author
Owner

@bytaesu commented on GitHub (Dec 29, 2025):

Image

Hi @techzealot,

As I understand it, the read-only headers property prevents reassigning the Headers object itself, but does not prevent using its methods. When I set up an Expo app myself by following the guide, I didn't run into this issue. Could you let me know which environment this happens in, or share a reproducible minimal repo?


Also, there was an issue with trustedOrigins in the recent 1.4.x patches, which was fixed in 1.4.9. I didn't see this issue when testing with 1.4.7, but could you check whether it still occurs on the latest version? It's possible this is related to that fix.

<!-- gh-comment-id:3697689884 --> @bytaesu commented on GitHub (Dec 29, 2025): <img width="818" height="126" alt="Image" src="https://github.com/user-attachments/assets/4f734948-9136-4072-86c9-2340248c0acb" /> Hi @techzealot, As I understand it, the read-only headers property prevents reassigning the Headers object itself, but does not prevent using its methods. When I set up an Expo app myself by following the guide, I didn't run into this issue. Could you let me know which environment this happens in, or share a reproducible minimal repo? --- Also, there was an issue with trustedOrigins in the recent `1.4.x` patches, which was fixed in `1.4.9`. I didn't see this issue when testing with `1.4.7`, but could you check whether it still occurs on the latest version? It's possible this is related to that fix. - https://github.com/better-auth/better-auth/pull/6887
Author
Owner

@techzealot commented on GitHub (Dec 30, 2025):

@bytaesu I tested the v1.4.9 version, the error still remains.
this is the minimal repo:
https://github.com/techzealot/better-auth-expo-origin-case

an expo client and a hono backend, and if you uncomment the test plugin "expoOriginFix" ,the error will disappear.
this shows we add header in the wrong way.
but I found another problem, if we use the expo plugin, any other following plugin's onRequest will not execute, I guess the onRequest chain is short-circuit once we return a new request.I think this is not good as the expo plugin occupy the only chance to use the onRequest.

<!-- gh-comment-id:3698131924 --> @techzealot commented on GitHub (Dec 30, 2025): @bytaesu I tested the v1.4.9 version, the error still remains. this is the minimal repo: https://github.com/techzealot/better-auth-expo-origin-case an expo client and a hono backend, and if you uncomment the test plugin "expoOriginFix" ,the error will disappear. this shows we add header in the wrong way. but I found another problem, if we use the expo plugin, any other following plugin's onRequest will not execute, I guess the onRequest chain is short-circuit once we return a new request.I think this is not good as the expo plugin occupy the only chance to use the onRequest.
Author
Owner

@bytaesu commented on GitHub (Dec 31, 2025):

Hi @techzealot,

After testing, request.clone() itself works fine.

In your case, this issue happens because @hono/node-server uses a pseudo Request object. Because of that, I found an edge case in how better-call (used internally by better-auth) handles Request, and I will fix it.

Also your point about the onRequest chain makes sense, and I'll fix that as well.

Thanks for providing the example repo. It really helped me identify the issue 🙏

<!-- gh-comment-id:3700886376 --> @bytaesu commented on GitHub (Dec 31, 2025): Hi @techzealot, After testing, `request.clone()` itself works fine. In your case, this issue happens because `@hono/node-server` uses a pseudo Request object. Because of that, I found an edge case in how `better-call` (used internally by `better-auth`) handles Request, and I will fix it. Also your point about the onRequest chain makes sense, and I'll fix that as well. Thanks for providing the example repo. It really helped me identify the issue 🙏
Author
Owner

@focux commented on GitHub (Jan 12, 2026):

Just a heads up, I tested it with the new version of better-call and the issue still persists. Using hono on the backend as well.

<!-- gh-comment-id:3739410236 --> @focux commented on GitHub (Jan 12, 2026): Just a heads up, I tested it with the new version of `better-call` and the issue still persists. Using `hono` on the backend as well.
Author
Owner

@bytaesu commented on GitHub (Jan 12, 2026):

@focux thanks for letting me know 🙏

I’ll check what’s missing

<!-- gh-comment-id:3739427992 --> @bytaesu commented on GitHub (Jan 12, 2026): @focux thanks for letting me know 🙏 I’ll check what’s missing
Author
Owner

@bytaesu commented on GitHub (Jan 12, 2026):

Just a heads up, I tested it with the new version of better-call and the issue still persists. Using hono on the backend as well.

Are you using v1.4.11 ??

<!-- gh-comment-id:3739430583 --> @bytaesu commented on GitHub (Jan 12, 2026): > Just a heads up, I tested it with the new version of `better-call` and the issue still persists. Using `hono` on the backend as well. Are you using v1.4.11 ??
Author
Owner

@focux commented on GitHub (Jan 12, 2026):

@bytaesu I'm using 1.5.0-beta.3 which I'm pretty sure have the fix but I might be wrong hah.

<!-- gh-comment-id:3739435460 --> @focux commented on GitHub (Jan 12, 2026): @bytaesu I'm using `1.5.0-beta.3` which I'm pretty sure have the fix but I might be wrong hah.
Author
Owner

@bytaesu commented on GitHub (Jan 12, 2026):

@focux Ok just for checking!

I will look into this, no worries 😄

<!-- gh-comment-id:3739442985 --> @bytaesu commented on GitHub (Jan 12, 2026): @focux Ok just for checking! I will look into this, no worries 😄
Author
Owner

@bytaesu commented on GitHub (Jan 15, 2026):

@bytaesu I'm using 1.5.0-beta.3 which I'm pretty sure have the fix but I might be wrong hah.

I checked, and this PR seems to have been missed in the following release.. I’ve shared this with the team 🙏

<!-- gh-comment-id:3756753881 --> @bytaesu commented on GitHub (Jan 15, 2026): > [@bytaesu](https://github.com/bytaesu) I'm using `1.5.0-beta.3` which I'm pretty sure have the fix but I might be wrong hah. I checked, and this PR seems to have been missed in the following release.. I’ve shared this with the team 🙏
Author
Owner

@focux commented on GitHub (Jan 29, 2026):

I tested again with v1.4.18 and still not working. I'm using Hono and still getting the: Can't modify immutable headers problem.

Also, I ran pnpm why better-call to know what version of better-call it was using and seems to be using v1.1.8 which is the one who's supposed to have the fix.

Edit
This is the workaround for now https://github.com/better-auth/better-auth/issues/5568#issuecomment-3617470030

<!-- gh-comment-id:3817926378 --> @focux commented on GitHub (Jan 29, 2026): I tested again with `v1.4.18` and still not working. I'm using Hono and still getting the: `Can't modify immutable headers problem`. Also, I ran `pnpm why better-call` to know what version of `better-call` it was using and seems to be using `v1.1.8` which is the one who's supposed to have the fix. **Edit** This is the workaround for now https://github.com/better-auth/better-auth/issues/5568#issuecomment-3617470030
Author
Owner

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

Hi @focux,

While building and testing an Expo app myself, I realized this issue still occurs in environments like Cloudflare Workers. I’ve applied a fix, and it will be included in the next release 🙏

<!-- gh-comment-id:3842661996 --> @bytaesu commented on GitHub (Feb 3, 2026): Hi @focux, While building and testing an Expo app myself, I realized this issue still occurs in environments like Cloudflare Workers. I’ve applied a fix, and it will be included in the next release 🙏
Author
Owner

@focux commented on GitHub (Feb 3, 2026):

@bytaesu Thank you so much! I'm looking forward to try the fix 🙏🏼

<!-- gh-comment-id:3842680544 --> @focux commented on GitHub (Feb 3, 2026): @bytaesu Thank you so much! I'm looking forward to try the fix 🙏🏼
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#28026