diff --git a/packages/expo/src/client.ts b/packages/expo/src/client.ts index a87c0109ea..860f56a3ac 100644 --- a/packages/expo/src/client.ts +++ b/packages/expo/src/client.ts @@ -396,7 +396,7 @@ export const expoClient = (opts: ExpoClientOptions) => { // Only process and notify if the Set-Cookie header contains better-auth cookies // This prevents infinite refetching when other cookies (like Cloudflare's __cf_bm) are present if (hasBetterAuthCookies(setCookie, cookiePrefix)) { - const prevCookie = await storage.getItem(cookieName); + const prevCookie = storage.getItem(cookieName); const toSetCookie = getSetCookie( setCookie || "", prevCookie ?? undefined, @@ -408,7 +408,7 @@ export const expoClient = (opts: ExpoClientOptions) => { store?.notify("$sessionSignal"); } else { // Still update the storage to refresh expiry times, but don't trigger refetch - await storage.setItem(cookieName, toSetCookie); + storage.setItem(cookieName, toSetCookie); } } } @@ -449,7 +449,7 @@ export const expoClient = (opts: ExpoClientOptions) => { } catch {} } - const storedCookieJson = await storage.getItem(cookieName); + const storedCookieJson = storage.getItem(cookieName); const oauthStateValue = getOAuthStateValue( storedCookieJson, cookiePrefix, @@ -468,9 +468,9 @@ export const expoClient = (opts: ExpoClientOptions) => { ); if (result.type !== "success") return; const url = new URL(result.url); - const cookie = String(url.searchParams.get("cookie")); + const cookie = url.searchParams.get("cookie"); if (!cookie) return; - const prevCookie = await storage.getItem(cookieName); + const prevCookie = storage.getItem(cookieName); const toSetCookie = getSetCookie(cookie, prevCookie ?? undefined); storage.setItem(cookieName, toSetCookie); store?.notify("$sessionSignal"); @@ -519,7 +519,7 @@ export const expoClient = (opts: ExpoClientOptions) => { } } if (url.includes("/sign-out")) { - await storage.setItem(cookieName, "{}"); + storage.setItem(cookieName, "{}"); store?.atoms.session?.set({ ...store.atoms.session.get(), data: null, diff --git a/packages/expo/test/expo.test.ts b/packages/expo/test/expo.test.ts index 3f2b5e8fd6..8a42e77762 100644 --- a/packages/expo/test/expo.test.ts +++ b/packages/expo/test/expo.test.ts @@ -427,6 +427,44 @@ describe("expo", async () => { }); }); + it("should not corrupt cookies when redirect URL has no cookie param", async () => { + await client.signIn.email({ + email: testUser.email, + password: testUser.password, + }); + + const storedCookieBefore = storage.get("better-auth_cookie"); + expect(storedCookieBefore).toBeDefined(); + const parsedCookieBefore = JSON.parse(storedCookieBefore || ""); + expect(parsedCookieBefore["better-auth.session_token"]).toBeDefined(); + + const expoWebBrowser = await import("expo-web-browser"); + + vi.mocked(expoWebBrowser.openAuthSessionAsync).mockResolvedValueOnce({ + type: "success", + url: "better-auth:///dashboard", // No cookie param + }); + + await client.signIn.social({ + provider: "google", + callbackURL: "/dashboard", + }); + + // Cookies should be preserved, not corrupted with "null" key + const storedCookieAfter = storage.get("better-auth_cookie"); + expect(storedCookieAfter).toBeDefined(); + const parsedCookieAfter = JSON.parse(storedCookieAfter || ""); + + // Should NOT have "null" as a cookie key + expect(parsedCookieAfter["null"]).toBeUndefined(); + + // Original session_token should still exist + expect(parsedCookieAfter["better-auth.session_token"]).toBeDefined(); + expect(parsedCookieAfter["better-auth.session_token"]?.value).toBe( + parsedCookieBefore["better-auth.session_token"]?.value, + ); + }); + it("should NOT include oauthState param in proxy URL when using database strategy", async () => { fn.mockClear();