Session Persistence Not Working – Forced Login Even After Fast Refresh and App Switch

When asking for help in this category, please make sure to provide the following details:

  • SDK Version: 8.1.0
  • Platform: Android/iOS
  • Browser Console Screenshots:
  • If the issue is related to Custom Authentication, please include the following information (optional):
    • Verifier Name:
    • JWKS Endpoint:
    • Sample idToken (JWT):

Also, kindly provide the Web3Auth initialization and login code snippet below. This will help us better understand your issue and provide you with the necessary assistance.

I’m integrating the Web3Auth React-Native PnP SDK into an Expo-based mobile app (Custom Dev Client). Regardless of reload type or app lifecycle, the session is never re-hydrated:

  • Fast Refresh / JS reload: Immediately after pressing “r” in Metro, I must log in again.
  • App switch (background ↔ foreground): After switching away and returning, session is lost.
  • Full process kill (app removed from recents): Naturally also prompts for re-login.

I have set sessionTime to the maximum (86400 seconds).

Initialization & Session-Restore Code

// mobileWalletConfig.ts
export const web3auth = new Web3Auth(WebBrowser, SecureStore, {
  clientId: "<MY_CLIENT_ID>",
  network: WEB3AUTH_NETWORK.CYAN,
  privateKeyProvider: new EthereumPrivateKeyProvider({ config: { chainConfig } }),
  redirectUrl: "com.myapp.mobile://auth",
  enableLogging: true,
  sessionTime: 86400, // 1 day
});

export const initWeb3Auth = async () => {
  await web3auth.init();
  return web3auth;
};
// Initialize Web3Auth and check for existing session
	useEffect(() => {
		const initWeb3Auth = async () => {
			try {
				await initWeb3AuthMobile();
				setWeb3auth(web3authInstance);
				console.log("web3authInstance", web3authInstance);

				// Check if user is already logged in
				if (web3authInstance.connected) {
					setIsConnecting(true);
					try {
						const privateKey = await web3authInstance.provider?.request({
							method: "eth_private_key",
						});

						if (typeof privateKey === "string") {
							const cleanPrivateKey = privateKey.startsWith("0x") ? privateKey.slice(2) : privateKey;
							const formattedPrivateKey = `0x${cleanPrivateKey}` as Hex;

							const account = privateKeyToAccount(formattedPrivateKey);
							setCurrentWallet(account.address);
							setIsSocial(true);
						}
					} catch (error) {
						console.error("Error restoring session:", error);
						// If there's an error restoring the session, log out
						await web3authInstance.logout();
					} finally {
						setIsConnecting(false);
					}
				}
			} catch (error) {
				console.error("Error initializing Web3Auth:", error);
			}
		};

		initWeb3Auth();
	}, []);

Login & Connector Code

// Trigger on user tap
await web3auth.login({
  loginProvider: LOGIN_PROVIDER.GOOGLE,
  redirectUrl: "com.myapp.mobile://auth",
  mfaLevel: "default",
});
// web3authRnConnector.ts
export function Web3AuthConnector({ web3AuthInstance, loginParams }) {
  return createConnector((config) => ({
    id: "web3Auth",
    name: "Web3Auth",
    type: "web3Auth",

    async connect() {
      if (!web3AuthInstance.connected) {
        await web3AuthInstance.login(loginParams);
      }
      const rawKey = await web3AuthInstance.provider!.request({ method: "eth_private_key" });
      const account = privateKeyToAccount(rawKey.startsWith("0x") ? rawKey : `0x${rawKey}`);
      const chainHex = await web3AuthInstance.provider!.request({ method: "eth_chainId" });
      return { accounts: [account.address], chainId: Number(chainHex) };
    },

    async isAuthorized() {
      return web3AuthInstance.connected;
    },

    async disconnect() {
      await web3AuthInstance.logout();
    },
    // …getAccounts, getChainId, event handlers…
  }));
}

Questions

  1. Should Fast Refresh or background→foreground transitions trigger a re-login? My understanding is that SecureStore data survives JS reloads—am I missing a step?
  2. Are there any additional native or Expo configuration steps required to ensure session data is re-hydrated?
  3. How can I verify that SecureStore is indeed persisting the Web3Auth session? Is there a recommended pattern to inspect or dump stored keys for debugging?
  4. Has anyone seen this behavior in a Custom Dev Client / standalone build and successfully kept the session alive until TTL expiry?

Any pointers, sample repos, or configuration tips would be greatly appreciated—thanks in advance!

1 Like

Thank you for the detailed breakdown and code snippets—this helps a lot in diagnosing the issue!

Summary of Your Issue

  • SDK & Version: Web3Auth Plug and Play (PnP) React Native SDK, v8.1.0
  • Platform: Expo-based React Native app (Custom Dev Client), Android & iOS
  • Session Issue: User is forced to re-login after Fast Refresh, JS reload, app background/foreground transitions, and app restarts—even with sessionTime set to 86400 seconds.
  • Storage: Using expo-secure-store as per Expo Managed Workflow.
  • Initialization & Login Code: Provided and appears correct.
  • Questions:
    1. Should Fast Refresh or app backgrounding require a re-login?
    2. Are extra native/Expo config steps needed for session persistence?
    3. How to verify SecureStore is persisting session data?
    4. Has anyone succeeded with session persistence in a Custom Dev Client?

Next Steps & Suggestions

1. Expected Behavior

  • Fast Refresh / JS Reload: SecureStore should persist session data across JS reloads and Fast Refresh. You should not need to re-login unless the session is expired or storage is cleared.
  • App Background/Foreground: Session should persist unless the app is killed and storage is wiped.
  • App Kill: Session should persist if SecureStore is working correctly, except on uninstall or device-level storage clear.

2. Expo & SecureStore Configuration Checklist

Please confirm the following:

  • App Scheme: Is your custom scheme (e.g., com.myapp.mobile) set in app.json under "expo": { "scheme": "com.myapp.mobile" }?
  • Expo Go: Are you testing in a Custom Dev Client or EAS build? (Web3Auth does not work in Expo Go.)
  • SecureStore Availability: Is SecureStore available and not running into permission issues? (Try writing/reading a test value to SecureStore outside of Web3Auth.)
  • Session Key: Are you using the default storage key or customizing it? (Default should work fine.)
  • SDK Initialization: Ensure Web3Auth is initialized only once at app start, not on every render or state change.

3. Debugging SecureStore

To check if SecureStore is persisting session data:

  • Try storing a test value using SecureStore directly and see if it persists across Fast Refresh and app backgrounding.
  • You can inspect SecureStore values via code (there’s no UI), e.g.:
    import * as SecureStore from 'expo-secure-store';
    SecureStore.getItemAsync('WEB3AUTH_SESSION_KEY').then(console.log);
    
    (Replace 'WEB3AUTH_SESSION_KEY' with the actual key Web3Auth uses, if known.)

4. Known Issues / Community Reports

  • There have been community reports of session loss if SecureStore is not properly configured or if running in Expo Go.
  • Ensure you’re using a Custom Dev Client or EAS build for testing.

5. Expo/React Native Fast Refresh Caveat

  • Fast Refresh in development can sometimes clear JS memory but should not clear SecureStore. If it does, check for accidental re-initialization or SecureStore errors in your logs.

Resources


Action Items for You

  1. Confirm you’re using a Custom Dev Client or EAS build (not Expo Go).
  2. Double-check your app.json scheme matches your redirect URL.
  3. Test SecureStore independently to verify persistence.
  4. Check for errors in the device logs related to SecureStore or Web3Auth session restoration.
  5. If possible, share any relevant logs or error messages during session restore attempts.

If you’ve already checked these and the issue persists, please let us know:

  • Any errors or warnings in your device logs (especially related to SecureStore or Web3Auth).
  • If you are seeing any differences between Android and iOS.
  • If you are using any custom logic to clear SecureStore or reset app state.

Thanks for your patience—let us know what you find, and we’ll help you get session persistence working!

Note: This response was generated by an AI bot and may require further verification. The Web3Auth support team has tracked this issue and will be responding as soon as possible.

Additionally, we would recommend you to join our biweekly Office Hours to get this resolved even faster. Web3Auth Developer Office Hours

Following!
Facing the same issue

Facing the same issue

Action Items:

  1. Confirm you’re using a Custom Dev Client or EAS build (not Expo Go).
    Yes, I am using a Custom Dev Client
  2. Double-check your app.json scheme matches your redirect URL.
    Checked the app.json, the schema matches the redirect URL.
  3. Test SecureStore independently to verify persistence.
    Verified the SecureStore by storing a temporary value, the value persisted across fast refresh and even after removing the app from the recents.
  4. Check for errors in the device logs related to SecureStore or Web3Auth session restoration.
    No errors while restoring the Web3Auth session.
  5. If possible, share any relevant logs or error messages during session restore attempts.
    Here is the web3Auth instance when trying to restore the session:
web3authInstance = {
  accountAbstractionProvider: undefined,
  addVersionInUrls: true,
  keyStore: {
    storage: {
      AFTER_FIRST_UNLOCK: 0,
      AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY: 1,
      ALWAYS: 2,
      ALWAYS_THIS_DEVICE_ONLY: 4,
      WHEN_PASSCODE_SET_THIS_DEVICE_ONLY: 3,
      WHEN_UNLOCKED: 5,
      WHEN_UNLOCKED_THIS_DEVICE_ONLY: 6,
      canUseBiometricAuthentication: [Function],
      deleteItemAsync: [Function],
      getItem: [Function],
      getItemAsync: [Function],
      isAvailableAsync: [Function],
      setItem: [Function],
      setItemAsync: [Function]
    }
  },
  options: {
    buildEnv: "production",
    clientId: <SOME_CLIENT_ID>,
    dashboardUrl: "https://account.web3auth.io/wallet/account",
    enableLogging: true,
    loginConfig: {},
    mfaSettings: {},
    network: "cyan",
    originData: {
      "...." : "...."
    },
    privateKeyProvider: {
      PROVIDER_CHAIN_NAMESPACE: "eip155",
      _events: { ... },
      _eventsCount: 3,
      _maxListeners: undefined,
      _providerEngineProxy: null,
      chainConfig: {
        blockExplorerUrl: "https://berascan.com",
        chainId: "0x138de",
        chainNamespace: "eip155",
        displayName: "Berachain",
        rpcTarget: "https://rpc.berachain.com",
        ticker: "BERA",
        tickerName: "Berachain"
      },
      defaultConfig: { ... },
      defaultState: { chainId: "loading" },
      disabled: false,
      initialConfig: { ... },
      internalConfig: { ... },
      internalState: { chainId: "loading" },
      keyExportEnabled: true,
      keyExportFlagSetByCode: false,
      name: "BaseController",
      networks: { "0x138de": { ... } }
    },
    redirectUrl: "com.app.mobile://auth",
    sdkUrl: "https://auth.web3auth.io",
    sessionTime: 3600,
    storageServerUrl: "https://session.web3auth.io",
    walletSdkURL: "https://wallet.web3auth.io",
    webauthnTransports: ["internal"],
    whiteLabel: {}
  },
  privateKeyProvider: { ...same as above... },
  ready: true,
  sessionManager: {
    allowedOrigin: "*",
    sessionId: "",
    sessionNamespace: undefined,
    sessionServerBaseUrl: "https://session.web3auth.io",
    sessionTime: 3600
  },
  state: {},
  webBrowser: {
    WebBrowserPresentationStyle: {
      AUTOMATIC: "automatic",
      CURRENT_CONTEXT: "currentContext",
      FORM_SHEET: "formSheet",
      FULL_SCREEN: "fullScreen",
      OVER_CURRENT_CONTEXT: "overCurrentContext",
      OVER_FULL_SCREEN: "overFullScreen",
      PAGE_SHEET: "pageSheet",
      POPOVER: "popover"
    },
    WebBrowserResultType: {
      CANCEL: "cancel",
      DISMISS: "dismiss",
      LOCKED: "locked",
      OPENED: "opened"
    },
    coolDownAsync: [Function],
    dismissAuthSession: [Function],
    dismissBrowser: [Function],
    getCustomTabsSupportingBrowsersAsync: [Function],
    mayInitWithUrlAsync: [Function],
    maybeCompleteAuthSession: [Function],
    openAuthSessionAsync: [Function],
    openBrowserAsync: [Function],
    warmUpAsync: [Function]
  }
};

Thanks for the detailed info, Sami.
Since you’re using a Custom Dev Client and SecureStore is persisting values, the issue likely stems from the sessionId not being properly stored or restored since it’s empty in your logs.

A few quick checks:

  • Make sure web3auth.init() runs only once on app start and not after login.
  • Avoid calling logout() in any error handler or useEffect that might run on refresh.
  • Ensure your app state (e.g., holding web3authInstance) isn’t resetting on Fast Refresh.

If the issue persists, a minimal repo could help debug further.