[GH-ISSUE #3872] best practice to call useSession in content script of browser extension #27070

Closed
opened 2026-04-17 17:52:15 -05:00 by GiteaMirror · 9 comments
Owner

Originally created by @mengxi-ream on GitHub (Aug 8, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/3872

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

The related issue is #1381

I'm thinking what's the best practice to use better auth in content script which send request from the host origin.

Have several ideas to solve (haven't verified):

  1. send message to background script to let background request api from better auth backend, and let the auth backend whitelist the background origin extension://xxxxx
  2. change fetch in createAuthClient, and write the logic in this fetch to send message to the backgroud script and return the results from there.
export const authClient = createAuthClient({
  baseURL: WEBSITE_URL,
  fetch: customFetch
})

Current vs. Expected behavior

current behavior:

Image

expected behavior:

no CORS problem

What version of Better Auth are you using?

1.2.12

Provide environment information

- OS: macOS
- Browser: Chrome

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

Client

Auth config (if applicable)

import type { BetterAuthOptions } from 'better-auth'
import { APP_NAME } from '@repo/constants'
import { env } from './env'

/**
 * Custom options for Better Auth
 *
 * Docs: https://www.better-auth.com/docs/reference/options
 */
export const betterAuthOptions: BetterAuthOptions = {
  /**
   * The name of the application.
   */
  appName: APP_NAME,
  /**
   * Base path for Better Auth.
   * @default "/api/auth"
   */
  // basePath: "/api",

  /**
   * Enable email and password authentication
   */
  emailAndPassword: {
    enabled: true,
    requireEmailVerification: false, // TODO: set to be true later
    minPasswordLength: 8,
    maxPasswordLength: 128,
  },
  socialProviders: {
    google: {
      prompt: 'select_account',
      clientId: env.GOOGLE_CLIENT_ID,
      clientSecret: env.GOOGLE_CLIENT_SECRET,
    },
  },
  // .... More options
}

Additional context

No response

Originally created by @mengxi-ream on GitHub (Aug 8, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/3872 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce The related issue is #1381 I'm thinking what's the best practice to use better auth in content script which send request from the host origin. Have several ideas to solve (haven't verified): 1. send message to background script to let background request api from better auth backend, and let the auth backend whitelist the background origin `extension://xxxxx` 2. change `fetch` in `createAuthClient`, and write the logic in this `fetch` to send message to the backgroud script and return the results from there. ``` export const authClient = createAuthClient({ baseURL: WEBSITE_URL, fetch: customFetch }) ``` ### Current vs. Expected behavior current behavior: <img width="998" height="164" alt="Image" src="https://github.com/user-attachments/assets/33c9b367-a9eb-49ce-ab91-f7503cfb8a1a" /> expected behavior: no CORS problem ### What version of Better Auth are you using? 1.2.12 ### Provide environment information ```bash - OS: macOS - Browser: Chrome ``` ### Which area(s) are affected? (Select all that apply) Client ### Auth config (if applicable) ```typescript import type { BetterAuthOptions } from 'better-auth' import { APP_NAME } from '@repo/constants' import { env } from './env' /** * Custom options for Better Auth * * Docs: https://www.better-auth.com/docs/reference/options */ export const betterAuthOptions: BetterAuthOptions = { /** * The name of the application. */ appName: APP_NAME, /** * Base path for Better Auth. * @default "/api/auth" */ // basePath: "/api", /** * Enable email and password authentication */ emailAndPassword: { enabled: true, requireEmailVerification: false, // TODO: set to be true later minPasswordLength: 8, maxPasswordLength: 128, }, socialProviders: { google: { prompt: 'select_account', clientId: env.GOOGLE_CLIENT_ID, clientSecret: env.GOOGLE_CLIENT_SECRET, }, }, // .... More options } ``` ### Additional context _No response_
GiteaMirror added the locked label 2026-04-17 17:52:15 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Aug 8, 2025):

The best practice is to customize the fetch function in your createAuthClient so that all Better Auth requests from the content script are relayed to the background script, which then performs the actual network request. This is necessary because content scripts can't make cross-origin requests directly—doing so will always result in CORS errors. By using message passing to the background script, you leverage the extension's permissions to bypass these restrictions, and you can return the result back to the content script seamlessly. This approach is fully supported by Better Auth's client design and is the recommended solution for browser extensions (issue #1381).

Your idea of customizing fetch in createAuthClient is spot on. Implement your custom fetch to send a message to the background script, have the background script perform the fetch, and then resolve the response back in the content script. This pattern keeps your session management and useSession usage unchanged, as all requests will transparently go through your custom transport (docs).

Make sure to whitelist your extension's background origin (e.g., extension://xxxxx) in the Better Auth backend's trustedOrigins, and double-check your cookie and CORS settings if you rely on session cookies (docs).

There’s no official Better Auth documentation for browser extensions, but this pattern is widely used for auth in extensions and is confirmed as the best practice in the Better Auth repo (issue #1381).

To reply, just mention @dosu.


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

<!-- gh-comment-id:3166584793 --> @dosubot[bot] commented on GitHub (Aug 8, 2025): <!-- Answer --> The best practice is to customize the fetch function in your createAuthClient so that all Better Auth requests from the content script are relayed to the background script, which then performs the actual network request. This is necessary because content scripts can't make cross-origin requests directly—doing so will always result in CORS errors. By using message passing to the background script, you leverage the extension's permissions to bypass these restrictions, and you can return the result back to the content script seamlessly. This approach is fully supported by Better Auth's client design and is the recommended solution for browser extensions ([issue #1381](https://github.com/better-auth/better-auth/issues/1381)). Your idea of customizing fetch in createAuthClient is spot on. Implement your custom fetch to send a message to the background script, have the background script perform the fetch, and then resolve the response back in the content script. This pattern keeps your session management and useSession usage unchanged, as all requests will transparently go through your custom transport ([docs](https://better-auth.com/docs/basic-usage)). Make sure to whitelist your extension's background origin (e.g., `extension://xxxxx`) in the Better Auth backend's trustedOrigins, and double-check your cookie and CORS settings if you rely on session cookies ([docs](https://better-auth.com/docs/concepts/cookies)). There’s no official Better Auth documentation for browser extensions, but this pattern is widely used for auth in extensions and is confirmed as the best practice in the Better Auth repo ([issue #1381](https://github.com/better-auth/better-auth/issues/1381)). <!-- 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/e766aed0-5a90-43e9-89f1-a335643639cd?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/e766aed0-5a90-43e9-89f1-a335643639cd?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/e766aed0-5a90-43e9-89f1-a335643639cd?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/e766aed0-5a90-43e9-89f1-a335643639cd?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/e766aed0-5a90-43e9-89f1-a335643639cd?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/e766aed0-5a90-43e9-89f1-a335643639cd?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/e766aed0-5a90-43e9-89f1-a335643639cd?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/3872)
Author
Owner

@Kinfe123 commented on GitHub (Aug 8, 2025):

have you checked out - browser extension guide here

<!-- gh-comment-id:3167238402 --> @Kinfe123 commented on GitHub (Aug 8, 2025): have you checked out - browser extension guide [here](http://better-auth.com/docs/guides/browser-extension-guide)
Author
Owner

@ananaBMaster commented on GitHub (Aug 8, 2025):

have you checked out - browser extension guide here

We have checked this. and it

  1. has no issue if we use better auth client in popup page
  2. has CORS issue if we use better auth client in content script (use it on a domain which is not trusted by the better auth server like google.com)

So we proxy all requests from content script to background script to solve the issue: b3a5a1e38b/apps/extension/src/utils/auth/auth-client.ts (L8)

// use this client in content script

export const authClient = createAuthClient({
  baseURL: WEBSITE_URL,
  fetchOptions: {
    // Use background proxy to avoid CORS in content scripts
    customFetchImpl: async (input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response> => {
      const inputUrl = typeof input === 'string' ? input : (input instanceof URL ? input.toString() : input.url)
      const url = inputUrl.startsWith('http')
        ? inputUrl
        : `${WEBSITE_URL}${inputUrl.startsWith('/') ? '' : '/'}${inputUrl}`

      const method = init?.method
      const headers = normalizeHeaders(init?.headers)
      const body = typeof init?.body === 'string' ? init.body : undefined

      const resp = await sendMessage('backgroundFetch', {
        url,
        method,
        headers,
        body,
        credentials: 'include',
      })

      return new Response(resp.body, {
        status: resp.status,
        statusText: resp.statusText,
        headers: new Headers(resp.headers),
      })
    },
  },
})
// set this in background script

onMessage('backgroundFetch', async (message) => {
    const { url, method, headers, body, credentials } = message.data
    const response = await fetch(url, {
      method: method ?? 'POST',
      headers: headers ? new Headers(headers) : undefined,
      body,
      credentials: credentials ?? 'include',
    })

    const responseHeaders: [string, string][] = Array.from(response.headers.entries())
    const textBody = await response.text()

    return {
      status: response.status,
      statusText: response.statusText,
      headers: responseHeaders,
      body: textBody,
    }
  })

I'm not certain if there's a better approach or if the existing code needs improvement.

<!-- gh-comment-id:3168610511 --> @ananaBMaster commented on GitHub (Aug 8, 2025): > have you checked out - browser extension guide [here](http://better-auth.com/docs/guides/browser-extension-guide) We have checked this. and it 1. has no issue if we use better auth client in popup page 2. has CORS issue if we use better auth client in content script (use it on a domain which is not trusted by the better auth server like google.com) So we proxy all requests from content script to background script to solve the issue: https://github.com/mengxi-ream/read-frog/blob/b3a5a1e38bd14a937317213984c7450d4840fd1c/apps/extension/src/utils/auth/auth-client.ts#L8 ```ts // use this client in content script export const authClient = createAuthClient({ baseURL: WEBSITE_URL, fetchOptions: { // Use background proxy to avoid CORS in content scripts customFetchImpl: async (input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response> => { const inputUrl = typeof input === 'string' ? input : (input instanceof URL ? input.toString() : input.url) const url = inputUrl.startsWith('http') ? inputUrl : `${WEBSITE_URL}${inputUrl.startsWith('/') ? '' : '/'}${inputUrl}` const method = init?.method const headers = normalizeHeaders(init?.headers) const body = typeof init?.body === 'string' ? init.body : undefined const resp = await sendMessage('backgroundFetch', { url, method, headers, body, credentials: 'include', }) return new Response(resp.body, { status: resp.status, statusText: resp.statusText, headers: new Headers(resp.headers), }) }, }, }) ``` ```ts // set this in background script onMessage('backgroundFetch', async (message) => { const { url, method, headers, body, credentials } = message.data const response = await fetch(url, { method: method ?? 'POST', headers: headers ? new Headers(headers) : undefined, body, credentials: credentials ?? 'include', }) const responseHeaders: [string, string][] = Array.from(response.headers.entries()) const textBody = await response.text() return { status: response.status, statusText: response.statusText, headers: responseHeaders, body: textBody, } }) ``` I'm not certain if there's a better approach or if the existing code needs improvement.
Author
Owner

@frectonz commented on GitHub (Aug 13, 2025):

@mengxi-ream according to this (https://www.chromium.org/Home/chromium-security/extension-content-script-fetches/) It is not recommended to perform cross-origin requests from content scripts.

The second option you suggested of providing a customFetchImpl that sends the request to the background script which will then proxy it to the auth server sounds good. You should go with that if there are multiple places where you use the authClient in the content script.

If not though, that is if you only call authClient at a couple of places in the content scripts, I recommend not going with the customFetchImpl and just using sendMessage to coordinate with the background script and have it call the methods on the authClient.

Also don't forget to check this guide, https://www.better-auth.com/docs/guides/browser-extension-guide#configure-the-server-auth-instance, and set the extension URL as a trusted origin in your auth config.


To move this forward, I suspect that there are two changes this issue might warrant

  1. Update to docs mentioning that authClient shouldn't be used in content scripts.
  2. A customFetchImpl for the authClient specifically meant to be used for when it is used in content scripts, better auth would need to also provide some kind of way to set up the proxy receiver on the background script side.
<!-- gh-comment-id:3182626148 --> @frectonz commented on GitHub (Aug 13, 2025): @mengxi-ream according to this (https://www.chromium.org/Home/chromium-security/extension-content-script-fetches/) It is not recommended to perform cross-origin requests from content scripts. The second option you suggested of providing a `customFetchImpl` that sends the request to the background script which will then proxy it to the auth server sounds good. You should go with that if there are multiple places where you use the `authClient` in the content script. If not though, that is if you only call `authClient` at a couple of places in the content scripts, I recommend not going with the `customFetchImpl` and just using `sendMessage` to coordinate with the background script and have it call the methods on the `authClient`. Also don't forget to check this guide, https://www.better-auth.com/docs/guides/browser-extension-guide#configure-the-server-auth-instance, and set the extension URL as a trusted origin in your auth config. ----- To move this forward, I suspect that there are two changes this issue might warrant 1. Update to docs mentioning that `authClient` shouldn't be used in content scripts. 2. A `customFetchImpl` for the `authClient` specifically meant to be used for when it is used in content scripts, better auth would need to also provide some kind of way to set up the proxy receiver on the background script side.
Author
Owner

@mengxi-ream commented on GitHub (Aug 13, 2025):

hi thank you for reply. my previous customFetchImpl works, I use authclientt a lot of places in the future, so may just stick with this solution

<!-- gh-comment-id:3182857585 --> @mengxi-ream commented on GitHub (Aug 13, 2025): hi thank you for reply. my previous customFetchImpl works, I use authclientt a lot of places in the future, so may just stick with this solution
Author
Owner

@frectonz commented on GitHub (Aug 13, 2025):

@mengxi-ream are you ok with me closing the issue then

<!-- gh-comment-id:3183039611 --> @frectonz commented on GitHub (Aug 13, 2025): @mengxi-ream are you ok with me closing the issue then
Author
Owner

@mengxi-ream commented on GitHub (Aug 13, 2025):

@mengxi-ream are you ok with me closing the issue then

yes please

<!-- gh-comment-id:3186000748 --> @mengxi-ream commented on GitHub (Aug 13, 2025): > @mengxi-ream are you ok with me closing the issue then yes please
Author
Owner

@frectonz commented on GitHub (Aug 14, 2025):

@mengxi-ream @ananaBMaster

We are planning on updating the documentation on the browser guide, to reflect what we tales about in this issue in this PR #3969

The PR updates the browser guide to have this "Usage Notes" section, what do y'all think about this?

Image
<!-- gh-comment-id:3187864236 --> @frectonz commented on GitHub (Aug 14, 2025): @mengxi-ream @ananaBMaster We are planning on updating the documentation on the browser guide, to reflect what we tales about in this issue in this PR #3969 The PR updates the browser guide to have this "Usage Notes" section, what do y'all think about this? <img width="1710" height="977" alt="Image" src="https://github.com/user-attachments/assets/9018b550-809c-475b-9ad8-389a664e9365" />
Author
Owner

@mengxi-ream commented on GitHub (Aug 14, 2025):

@mengxi-ream @ananaBMaster

We are planning on updating the documentation on the browser guide, to reflect what we tales about in this issue in this PR #3969

The PR updates the browser guide to have this "Usage Notes" section, what do y'all think about this?

Image

Hi, thank you for the document!

I think content script can communicate with auth server in the case when the backend server allow the host page's origin. like if the backend living in Next.js web app (www.readfrog.app) allow content script to use authClient when users are on the readfrog.app website.

But for most cases, there is CORS problem because the backend auth server should better not allow all origin like *.

So to proxy the request from content script. the main purpose is for people don't wanna let the auth server trust all origins like *

And I believe we can also mention the method like customizing customFetchImpl to tell developers that they can proxy all fetch by doing this.

Could you please add this repository as an example to address this issue in the document? The link is https://github.com/mengxi-ream/read-frog. I believe it would be beneficial for people to see what a production-ready example looks like.

<!-- gh-comment-id:3189205382 --> @mengxi-ream commented on GitHub (Aug 14, 2025): > [@mengxi-ream](https://github.com/mengxi-ream) [@ananaBMaster](https://github.com/ananaBMaster) > > We are planning on updating the documentation on the browser guide, to reflect what we tales about in this issue in this PR [#3969](https://github.com/better-auth/better-auth/pull/3969) > > The PR updates the browser guide to have this "Usage Notes" section, what do y'all think about this? > > <img alt="Image" width="1710" height="977" src="https://private-user-images.githubusercontent.com/53809656/477958005-9018b550-809c-475b-9ad8-389a664e9365.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NTUxOTA4NjksIm5iZiI6MTc1NTE5MDU2OSwicGF0aCI6Ii81MzgwOTY1Ni80Nzc5NTgwMDUtOTAxOGI1NTAtODA5Yy00NzViLTlhZDgtMzg5YTY2NGU5MzY1LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA4MTQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwODE0VDE2NTYwOVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTMzNmU5ZDBkOTg1OTVlMzNmMzgwNzU4YThjYzNhYmEyMDhiY2RjYTExNDc5Y2VhZmEzZDUwNjI3Zjk0OTE2NTcmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.yLpE6yc_dVstlXAE2KuUjcS84J8PXmjNFa9aEsC3GIg"> Hi, thank you for the document! I think content script can communicate with auth server in the case when the backend server allow the host page's origin. like if the backend living in Next.js web app (`www.readfrog.app`) allow content script to use authClient when users are on the `readfrog.app` website. But for most cases, there is CORS problem because the backend auth server should better not allow all origin like `*`. So to proxy the request from content script. the main purpose is for people don't wanna let the auth server trust all origins like `*` And I believe we can also mention the method like customizing `customFetchImpl` to tell developers that they can proxy all `fetch` by doing this. Could you please add this repository as an example to address this issue in the document? The link is https://github.com/mengxi-ream/read-frog. I believe it would be beneficial for people to see what a production-ready example looks like.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#27070