Skip to content

Conversation

@AkaraChen
Copy link

@AkaraChen AkaraChen commented Jan 23, 2026

Summary

  • Use window.localStorage as fallback when Platform.OS === "web"
  • Fixes silent failure of better-auth client on web platform

Closes #844

Summary by CodeRabbit

  • Bug Fixes
    • Fixed authentication storage to dynamically select the appropriate storage mechanism based on platform—local storage on web, secure storage on native environments.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Jan 23, 2026

@AkaraChen is attempting to deploy a commit to the Better T Stack Team on Vercel.

A member of the Team first needs to authorize it.

@dosubot dosubot bot added size:S This PR changes 10-29 lines, ignoring generated files. bug Something isn't working labels Jan 23, 2026
@AkaraChen
Copy link
Author

I have no idea why the generated template file has so many diffs.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 23, 2026

Walkthrough

Introduces platform-aware storage selection in authClient templates. Add Platform import and modify storage logic to conditionally use window.localStorage on web and SecureStore on native platforms. No public API changes.

Changes

Cohort / File(s) Summary
Auth-client template storage configuration
packages/template-generator/templates/auth/better-auth/convex/native/base/lib/auth-client.ts.hbs, packages/template-generator/templates/auth/better-auth/native/base/lib/auth-client.ts.hbs
Added Platform import and conditional storage selection: uses window.localStorage when Platform.OS === "web", otherwise defaults to SecureStore for native environments

Possibly related PRs

  • #634: Modifies the same auth-client template files and storage configuration logic for platform-specific behavior.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding web platform support by using localStorage fallback for expoClient storage instead of always using SecureStore.
Linked Issues check ✅ Passed The PR successfully implements the fix specified in #844 by adding Platform.OS === 'web' checks to use window.localStorage on web instead of SecureStore, directly addressing the silent failure issue.
Out of Scope Changes check ✅ Passed All changes are scoped to the auth-client template files and directly related to the objective of adding web platform support for storage, with no unrelated modifications present.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/template-generator/templates/auth/better-auth/native/base/lib/auth-client.ts.hbs (1)

14-16: Consistent implementation with the convex variant — same verification applies.

The storage interface compatibility concern raised in the convex template applies here as well. Once verified that expoClient accepts both localStorage and SecureStore interfaces, this implementation correctly addresses the web platform support objective.

Comment on lines +15 to +17
storage: Platform.OS === "web"
? window.localStorage
: SecureStore,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

better-auth expo client storage interface documentation

💡 Result:

Better Auth’s Expo client plugin (expoClient) documents the storage option in the Expo Integration guide, where it shows passing expo-secure-store as the storage provider. [1]

In practice, that means storage should be an object that looks like Expo SecureStore’s module API—i.e. it can store, read, and delete string values by key using:

  • setItemAsync(key: string, value: string, options?)
  • getItemAsync(key: string, options?)
  • deleteItemAsync(key: string, options?) [2]

Example from the Better Auth docs (passing SecureStore directly):

import { createAuthClient } from "better-auth/react";
import { expoClient } from "@better-auth/expo/client";
import * as SecureStore from "expo-secure-store";

export const authClient = createAuthClient({
  baseURL: "http://localhost:8081",
  plugins: [
    expoClient({
      scheme: "myapp",
      storagePrefix: "myapp",
      storage: SecureStore,
    }),
  ],
});

[1]

Note: Expo SecureStore keys are restricted (alphanumeric plus ., -, _), so if your Better Auth storage keys don’t meet that requirement you may need an adapter / different prefixing strategy. [2][3]

Sources: [1][2][3]


🌐 Web query:

@better-auth/expo SecureStore localStorage compatibility

💡 Result:

@better-auth/expo’s client plugin (expoClient) expects you to pass a “storage” implementation (their docs show expo-secure-store) to cache session data/cookies. (better-auth.com)

1) SecureStore is not a drop-in “localStorage-compatible” solution (especially on web)

  • expo-secure-store has long-standing limitations on web (it doesn’t behave like “store in localStorage and it works the same”). (github.com)
  • It also enforces key-name restrictions: keys must match ^[\w.-]+$ (only alphanumeric/underscore plus . and -), so characters like : are invalid. (app.unpkg.com)

2) Better Auth + SecureStore key incompatibility (the : problem)

A reported Better Auth Expo issue notes that the plugin generates storage keys with colon separators like myapp:cookie / myapp:session_data, which triggers SecureStore’s “Invalid key” error and breaks persistence. (github.com)

What to do

  • On native (iOS/Android): you can use SecureStore, but you may need a key-mangling adapter (e.g., replace : with _) until/if the plugin avoids colons. (github.com)
  • On web: use a web storage implementation (e.g., localStorage/sessionStorage) instead of SecureStore, by passing a different storage to expoClient on Platform.OS === "web" (Better Auth explicitly supports providing your own storage). (better-auth.com)

Citations:


🌐 Web query:

better-auth storage options async sync compatibility

💡 Result:

Better Auth “storage” (where it keeps sessions / other auth state) typically falls into three buckets:

  1. Database (primary storage)
  • Default when you configure database: sessions are stored in the DB. (better-auth.com)
  1. Secondary storage (key-value store like Redis/KV)
  • Configure secondaryStorage: { get, set, delete } and Better Auth will store sessions in the secondary store by default (instead of the DB), unless you force DB storage with session.storeSessionInDatabase: true. (better-auth.com)
  • The documented SecondaryStorage interface is async (Promise-based) (get/set/delete return Promise). (better-auth.com)
  1. Cookie cache / stateless
  • You can enable cookie caching (session.cookieCache) to reduce DB hits, or run fully stateless (no DB config) where session data is stored/validated via signed/encrypted cookies. (better-auth.com)

Async vs sync compatibility

  • Global secondaryStorage: documented as Promise-returning (async). (better-auth.com)
  • Some plugin-level “custom storage” hooks (example: API Key plugin customStorage) explicitly allow either sync or async methods (return Promise<…> | …). (better-auth.com)

Citations:


Fix storage interface incompatibility: expoClient expects async storage, but localStorage is synchronous.

expoClient is designed for async storage (SecureStore-like API with getItemAsync, setItemAsync, deleteItemAsync). Passing window.localStorage on web violates this expectation. Additionally, better-auth generates storage keys with colons (e.g., myapp:cookie), which SecureStore rejects due to its key restrictions (alphanumeric, ., - only).

For web, pass an async storage implementation to expoClient (e.g., AsyncStorage or a custom async wrapper). For native, either use SecureStore with a key-mangling adapter to replace colons, or consider an alternative storage strategy. See: better-auth issue #5426.

@claudesortwell
Copy link

claudesortwell commented Jan 27, 2026

I encountered the same issue with expo web but I believe the better fix is this:

const trpcClient = createTRPCClient<AppRouter>({
  links: [
    httpBatchLink({
      url: `${env.EXPO_PUBLIC_SERVER_URL}/trpc`,
      fetch:
        Platform.OS !== "web"
          ? undefined
          : (url, options) => {
              return fetch(url, {
                ...options,
                credentials: "include",
              });
            },
      headers() {
        if (Platform.OS === "web") {
          return {};
        }
        const headers = new Map<string, string>();
        const cookies = authClient.getCookie();
        if (cookies) {
          headers.set("Cookie", cookies);
        }
        return Object.fromEntries(headers);
      },
    }),
  ],
});

Because automatically by default BetterAuth sends proper cookie headers and the browser automatically responds with it. Instead of storing the cookie in localStorage for web we should override where we call authClient.getCookie for web.

Though i get the intention that this repo does not really support expo web since its not a web option from the cli

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working size:S This PR changes 10-29 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

expoClient storage silently fails on web platform

2 participants