Expo not retaining session or cookie in SecureStore after social auth #675

Open
opened 2026-03-13 07:59:53 -05:00 by GiteaMirror · 9 comments
Owner

Originally created by @dalinarkholin on GitHub (Feb 14, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Create a express backend
  2. Create a Expo app
  3. Follow docs for Expo Integration and general installation instructions for a google provider auth flow.

Current vs. Expected behavior

Currently after the authClient.signIn.social(withOpts) call completes. The cookie and session_data are not stored within SecureStore, or at least anywhere I have found including {prefix}_cookie better-auth_cookie, {prefix}_session_data, etc

I expect these to be populated for future use.

What version of Better Auth are you using?

1.1.17

Provide environment information

Apple M1 Pro

"expo": "~52.0.33",
"expo-secure-store": "~14.0.1",

Node 22.13.1

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

Client

Auth config (if applicable)

export const auth = betterAuth({
  database: drizzleAdapter(db, {
    provider: "pg",
  }),
  plugins: [expo()],
  socialProviders: {
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    },
  },
  trustedOrigins: ["exp://", "http://localhost:3000"],
});

Additional context

Downgrading to:

"@better-auth/expo": "1.0.8",
"better-auth": "1.0.8",

for both the client and server, sort of works. It stores the cookie within {prefix}_cookie and then calling authClient.getSession() populates the {prefix}_session_data. This is my current workaround.

I have pulled down the repo, and wrote a unit test for this issue, but it passed. Which indicates to me it's more of a integration issue. As far as I could tell SecureStore works properly. I believe it's an issue with how the response is handled from the authClient.signIn.socal() function.

Originally created by @dalinarkholin on GitHub (Feb 14, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Create a express backend 2. Create a Expo app 3. Follow docs for Expo Integration and general installation instructions for a google provider auth flow. ### Current vs. Expected behavior Currently after the authClient.signIn.social(withOpts) call completes. The cookie and session_data are not stored within SecureStore, or at least anywhere I have found including `{prefix}_cookie` `better-auth_cookie`, `{prefix}_session_data`, etc I expect these to be populated for future use. ### What version of Better Auth are you using? 1.1.17 ### Provide environment information ```bash Apple M1 Pro "expo": "~52.0.33", "expo-secure-store": "~14.0.1", Node 22.13.1 ``` ### Which area(s) are affected? (Select all that apply) Client ### Auth config (if applicable) ```typescript export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "pg", }), plugins: [expo()], socialProviders: { google: { clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, }, }, trustedOrigins: ["exp://", "http://localhost:3000"], }); ``` ### Additional context Downgrading to: ``` "@better-auth/expo": "1.0.8", "better-auth": "1.0.8", ``` for both the client and server, sort of works. It stores the cookie within `{prefix}_cookie` and then calling authClient.getSession() populates the `{prefix}_session_data`. This is my current workaround. I have pulled down the repo, and wrote a unit test for this issue, but it passed. Which indicates to me it's more of a integration issue. As far as I could tell SecureStore works properly. I believe it's an issue with how the response is handled from the authClient.signIn.socal() function.
GiteaMirror added the bug label 2026-03-13 07:59:53 -05:00
Author
Owner

@Bekacru commented on GitHub (Feb 14, 2025):

Try the latest version 1.1.18 and let me know if this is still an issue.

@Bekacru commented on GitHub (Feb 14, 2025): Try the latest version `1.1.18` and let me know if this is still an issue.
Author
Owner

@dalinarkholin commented on GitHub (Feb 14, 2025):

Hmm, I did try v1.2.0-beta.4 yesterday and it was a no go, though I did have a lot of variables at play. I can give it another go and i'll report back.

@dalinarkholin commented on GitHub (Feb 14, 2025): Hmm, I did try `v1.2.0-beta.4` yesterday and it was a no go, though I did have a lot of variables at play. I can give it another go and i'll report back.
Author
Owner

@dalinarkholin commented on GitHub (Feb 17, 2025):

I updated to 1.1.18 and was able to see the {prefix}_cookie stored, however, {prefix}_session_data is still not populated after:

await authClient.signIn.social({
      provider, // "google"
      callbackURL: "/",
    });

And using:

const { data } = authClient.useSession(); after calling the above, results in null for data

It's not until after I call: await authClient.getSession(); that I have access to the session via SecureStore on the expo client. After calling getSession() the session is finally stored within {prefix}_session_data

It's not explicitly said, but the docs here: https://www.better-auth.com/docs/integrations/expo
lend me to believe that the session will be available via the useSession hook once the call to authClient.signIn.social completes successfully

So if someone else is coming across this, the latest version 1.1.18 will work, as long as you call "authClient.getSession()" directly after the social call, authClient.signIn.social.

@Bekacru do you know if the intention was for the data to be available directly after authClient.signIn.social? If no, then we should probably update the docs. If yes, then this seems like a valid bug.

@dalinarkholin commented on GitHub (Feb 17, 2025): I updated to `1.1.18` and was able to see the `{prefix}_cookie` stored, however, `{prefix}_session_data` is still not populated after: ``` await authClient.signIn.social({ provider, // "google" callbackURL: "/", }); ``` And using: `const { data } = authClient.useSession();` after calling the above, results in null for `data` It's not until after I call: `await authClient.getSession();` that I have access to the session via SecureStore on the expo client. After calling getSession() the session is finally stored within `{prefix}_session_data` It's not explicitly said, but the docs here: https://www.better-auth.com/docs/integrations/expo lend me to believe that the session will be available via the useSession hook once the call to authClient.signIn.social completes successfully So if someone else is coming across this, the latest version 1.1.18 will work, as long as you call "authClient.getSession()" directly after the social call, authClient.signIn.social. @Bekacru do you know if the intention was for the data to be available directly after authClient.signIn.social? If no, then we should probably update the docs. If yes, then this seems like a valid bug.
Author
Owner

@Rakhsan commented on GitHub (Apr 29, 2025):

any new updates on this issue it is not fixed in ^1.2.7

@Rakhsan commented on GitHub (Apr 29, 2025): any new updates on this issue it is not fixed in ^1.2.7
Author
Owner

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

await authClient.signIn.social({ provider: "github", callbackURL: "app://home", newUserCallbackURL: "app://name" }, {
        onSuccess: (ctx) => {
              console.log("success")
          },
          onError: (ctx) => {
              console.log("error")
          },
      });
await authClient.getSession();

this just works not sure why they are not fixing this in the latest version

@Rakhsan commented on GitHub (Apr 30, 2025): ``` await authClient.signIn.social({ provider: "github", callbackURL: "app://home", newUserCallbackURL: "app://name" }, { onSuccess: (ctx) => { console.log("success") }, onError: (ctx) => { console.log("error") }, }); await authClient.getSession(); ``` this just works not sure why they are not fixing this in the latest version
Author
Owner

@kayotimoteo commented on GitHub (Jun 26, 2025):

The same problem here, the data is saved in the SecureStore, but with each use of the authClient.useSession() storage is ignored and a new request is performed by the session.
I tried to see Better Auth's code to identify the problem because it is an Open Source project and contributing, without success.

@kayotimoteo commented on GitHub (Jun 26, 2025): The same problem here, the data is saved in the SecureStore, but with each use of the authClient.useSession() storage is ignored and a new request is performed by the session. I tried to see Better Auth's code to identify the problem because it is an Open Source project and contributing, without success.
Author
Owner

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

Hi, @dalinarkholin. 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 in an Expo app using Better Auth v1.1.17, session data isn't stored in SecureStore immediately after calling authClient.signIn.social().
  • Downgrading to v1.0.8 partially resolved the issue, but upgrading to v1.1.18 changed behavior so cookies are stored but session data only appears after explicitly calling authClient.getSession().
  • You asked if this delayed session availability is intended or a bug, suggesting documentation updates if it is intended.
  • Other users have confirmed the issue persists in later versions, noting that useSession triggers new requests instead of using stored session data.

Next Steps:

  • 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 don’t hear back within 7 days, I will automatically close this issue to help keep the backlog manageable.

Thank you for your understanding and contribution!

@dosubot[bot] commented on GitHub (Sep 25, 2025): Hi, @dalinarkholin. 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 in an Expo app using Better Auth v1.1.17, session data isn't stored in SecureStore immediately after calling `authClient.signIn.social()`. - Downgrading to v1.0.8 partially resolved the issue, but upgrading to v1.1.18 changed behavior so cookies are stored but session data only appears after explicitly calling `authClient.getSession()`. - You asked if this delayed session availability is intended or a bug, suggesting documentation updates if it is intended. - Other users have confirmed the issue persists in later versions, noting that `useSession` triggers new requests instead of using stored session data. **Next Steps:** - 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 don’t hear back within 7 days, I will automatically close this issue to help keep the backlog manageable. Thank you for your understanding and contribution!
Author
Owner

@Bekacru commented on GitHub (Nov 17, 2025):

We don't store session data in cookies anymore. we do session call for all useSession get requests. Is there an issue with persisting the actual session token?

@Bekacru commented on GitHub (Nov 17, 2025): We don't store session data in cookies anymore. we do session call for all `useSession` get requests. Is there an issue with persisting the actual session token?
Author
Owner

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

We don't store session data in cookies anymore. we do session call for all useSession get requests. Is there an issue with persisting the actual session token?

@better-auth/expo doesn't restore cached session data on app startup, causing offline users to be logged out

Problem

When using @better-auth/expo with useSession(), users are logged out when the app restarts without network connectivity, even though valid session data is already stored in SecureStore.

Current Behavior

  1. User logs in successfully
  2. Expo client correctly stores session data to SecureStore (${storagePrefix}_session_data and ${storagePrefix}_cookie)
  3. App restarts (or is killed and reopened)
  4. useSession() initializes with { data: null, isPending: true }
  5. useSession() immediately triggers a /api/auth/get-session request
  6. With network: Request succeeds → session restored → works fine
  7. Without network: Request fails → { data: null, error: ... } → user appears logged out

Expected Behavior

When the app starts offline, useSession() should:

  1. First check SecureStore for cached session data
  2. If valid cached data exists (not expired), use it as the initial state
  3. Optionally validate with server when network becomes available

Root Cause

Looking at the source code:

In @better-auth/expo/dist/client.mjs, the expo client correctly writes session data to storage:

if (context.request.url.toString().includes("/get-session") && !opts?.disableCache) {
  const data = context.data;
  storage.setItem(localCacheName, JSON.stringify(data));
}

However, in better-auth/dist/proxy-DNjQepc2.mjs, the session atom is initialized without reading from cache:

function getSessionAtom($fetch, options) {
  const $signal = atom(false);
  const session = useAuthQuery($signal, "/get-session", $fetch, { method: "GET" });
  // No cache restoration here
}

The expo client implements "write to cache" but not "read from cache as fallback".

Environment

  • better-auth: ^1.4.6
  • @better-auth/expo: ^1.4.6

Additional Issue

Calling /api/auth/get-session on every useSession call is unnecessary for mobile apps(the official docs say useSeesion cached session data, but now no cache was used). Unlike web browsers where cookies are managed by the browser, mobile apps have full control over stored credentials. If valid session data exists locally and hasn't expired, there's no need to make a network request just to get the same data back.

Suggested Solution

The expo client should restore cached session data on initialization:

  1. On client init, read ${storagePrefix}_session_data from SecureStore
  2. Check if session is not expired (compare expiresAt with current time)
  3. If valid, set it as the initial atom value and skip the /get-session request
  4. Only call /get-session when:
    • No cached data exists
    • Cached session has expired
    • Explicitly requested (e.g., pull-to-refresh)
    • App comes back to foreground after a long time (optional)

This would enable offline-first authentication for mobile apps and reduce unnecessary network requests.

@techzealot commented on GitHub (Dec 13, 2025): > We don't store session data in cookies anymore. we do session call for all `useSession` get requests. Is there an issue with persisting the actual session token? # `@better-auth/expo` doesn't restore cached session data on app startup, causing offline users to be logged out ## Problem When using `@better-auth/expo` with `useSession()`, users are logged out when the app restarts without network connectivity, even though valid session data is already stored in SecureStore. ## Current Behavior 1. User logs in successfully 2. Expo client correctly stores session data to SecureStore (`${storagePrefix}_session_data` and `${storagePrefix}_cookie`) 3. App restarts (or is killed and reopened) 4. `useSession()` initializes with `{ data: null, isPending: true }` 5. `useSession()` immediately triggers a `/api/auth/get-session` request 6. **With network**: Request succeeds → session restored → works fine 7. **Without network**: Request fails → `{ data: null, error: ... }` → user appears logged out ## Expected Behavior When the app starts offline, `useSession()` should: 1. First check SecureStore for cached session data 2. If valid cached data exists (not expired), use it as the initial state 3. Optionally validate with server when network becomes available ## Root Cause Looking at the source code: In `@better-auth/expo/dist/client.mjs`, the expo client correctly **writes** session data to storage: ```javascript if (context.request.url.toString().includes("/get-session") && !opts?.disableCache) { const data = context.data; storage.setItem(localCacheName, JSON.stringify(data)); } ``` However, in `better-auth/dist/proxy-DNjQepc2.mjs`, the session atom is initialized without reading from cache: ```javascript function getSessionAtom($fetch, options) { const $signal = atom(false); const session = useAuthQuery($signal, "/get-session", $fetch, { method: "GET" }); // No cache restoration here } ``` The expo client implements "write to cache" but not "read from cache as fallback". ## Environment - `better-auth`: ^1.4.6 - `@better-auth/expo`: ^1.4.6 ## Additional Issue Calling `/api/auth/get-session` on every useSession call is unnecessary for mobile apps(the official docs say useSeesion cached session data, but now no cache was used). Unlike web browsers where cookies are managed by the browser, mobile apps have full control over stored credentials. If valid session data exists locally and hasn't expired, there's no need to make a network request just to get the same data back. ## Suggested Solution The expo client should restore cached session data on initialization: 1. On client init, read `${storagePrefix}_session_data` from SecureStore 2. Check if session is not expired (compare `expiresAt` with current time) 3. If valid, set it as the initial atom value and skip the `/get-session` request 4. Only call `/get-session` when: - No cached data exists - Cached session has expired - Explicitly requested (e.g., pull-to-refresh) - App comes back to foreground after a long time (optional) This would enable offline-first authentication for mobile apps and reduce unnecessary network requests.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#675