[GH-ISSUE #6409] lastLoginMethod in Expo #27831

Closed
opened 2026-04-17 19:03:38 -05:00 by GiteaMirror · 3 comments
Owner

Originally created by @jinsley8 on GitHub (Nov 29, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/6409

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Install lastLoginMethod plugin to use in an Expo project

Current vs. Expected behavior

In Expo, lastLoginMethod is null.

The authClient.getLastUsedLoginMethod() reads from a cookie, which doesn't work in React Native/Expo. The Expo client uses SecureStore for auth tokens, not browser cookies.

What version of Better Auth are you using?

1.4.3

System info

In a monorepo so this isn't 100% correct:
{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 24.6.0: Wed Oct 15 21:12:06 PDT 2025; root:xnu-11417.140.69.703.14~1/RELEASE_ARM64_T6000",
    "release": "24.6.0",
    "cpuCount": 10,
    "cpuModel": "Apple M1 Pro",
    "totalMemory": "32.00 GB",
    "freeMemory": "0.20 GB"
  },
  "node": {
    "version": "v22.16.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "10.9.2"
  },
  "frameworks": [
    {
      "name": "react",
      "version": "catalog:react19"
    }
  ],
  "databases": null,
  "betterAuth": {
    "version": "catalog:",
    "config": null
  }
}

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

Client, Backend

Auth config (if applicable)

export const authClient = createAuthClient({
	baseURL: process.env.EXPO_PUBLIC_SERVER_URL,
	disableDefaultFetchPlugins: true,
	plugins: [
		expoClient({
			scheme: "appName",
			storagePrefix: "appName",
			storage: SecureStore,
		}),
		customSessionClient<MobileAuth>(),
		inferAdditionalFields({
			user: {
				firstName: { type: "string", required: false },
				lastName: { type: "string", required: false },
				preferredName: { type: "string", required: false },
				onboardingComplete: { type: "boolean", required: false },
				activeRole: { type: "string", required: false },
			},
		}),
		usernameClient(),
		lastLoginMethodClient(),
		stripeClient({
			subscription: false, // Disable subscription management as requested
		}),
	],
});

Additional context

No response

Originally created by @jinsley8 on GitHub (Nov 29, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/6409 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce Install lastLoginMethod plugin to use in an Expo project ### Current vs. Expected behavior In Expo, lastLoginMethod is null. The `authClient.getLastUsedLoginMethod()` reads from a cookie, which doesn't work in React Native/Expo. The Expo client uses `SecureStore` for auth tokens, not browser cookies. ### What version of Better Auth are you using? 1.4.3 ### System info ```bash In a monorepo so this isn't 100% correct: { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 24.6.0: Wed Oct 15 21:12:06 PDT 2025; root:xnu-11417.140.69.703.14~1/RELEASE_ARM64_T6000", "release": "24.6.0", "cpuCount": 10, "cpuModel": "Apple M1 Pro", "totalMemory": "32.00 GB", "freeMemory": "0.20 GB" }, "node": { "version": "v22.16.0", "env": "development" }, "packageManager": { "name": "npm", "version": "10.9.2" }, "frameworks": [ { "name": "react", "version": "catalog:react19" } ], "databases": null, "betterAuth": { "version": "catalog:", "config": null } } ``` ### Which area(s) are affected? (Select all that apply) Client, Backend ### Auth config (if applicable) ```typescript export const authClient = createAuthClient({ baseURL: process.env.EXPO_PUBLIC_SERVER_URL, disableDefaultFetchPlugins: true, plugins: [ expoClient({ scheme: "appName", storagePrefix: "appName", storage: SecureStore, }), customSessionClient<MobileAuth>(), inferAdditionalFields({ user: { firstName: { type: "string", required: false }, lastName: { type: "string", required: false }, preferredName: { type: "string", required: false }, onboardingComplete: { type: "boolean", required: false }, activeRole: { type: "string", required: false }, }, }), usernameClient(), lastLoginMethodClient(), stripeClient({ subscription: false, // Disable subscription management as requested }), ], }); ``` ### Additional context _No response_
GiteaMirror added the locked label 2026-04-17 19:03:38 -05:00
Author
Owner

@better-auth-agent[bot] commented on GitHub (Nov 29, 2025):

Thanks for reporting this issue. It looks like the lastLoginMethod plugin is designed to read from a browser cookie, which isn’t available in Expo since the auth tokens are stored in SecureStore. This mismatch in token storage explains why lastLoginMethod is coming back as null in Expo environments. One next step is to modify or extend the lastLoginMethod logic so it retrieves the token (or the corresponding login method) from SecureStore rather than from cookies. Alternatively, consider a custom implementation if your project needs to strictly rely on SecureStore for such metadata.

Does adjusting the retrieval method from SecureStore help in your case, or would you like assistance exploring a tailored solution? Tag @better-auth-agent if you would like an answer from the Agent.

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3591851598 --> @better-auth-agent[bot] commented on GitHub (Nov 29, 2025): Thanks for reporting this issue. It looks like the lastLoginMethod plugin is designed to read from a browser cookie, which isn’t available in Expo since the auth tokens are stored in SecureStore. This mismatch in token storage explains why lastLoginMethod is coming back as null in Expo environments. One next step is to modify or extend the lastLoginMethod logic so it retrieves the token (or the corresponding login method) from SecureStore rather than from cookies. Alternatively, consider a custom implementation if your project needs to strictly rely on SecureStore for such metadata. Does adjusting the retrieval method from SecureStore help in your case, or would you like assistance exploring a tailored solution? Tag @better-auth-agent if you would like an answer from the Agent. <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) • [Discord](https://discord.gg/better-auth) • [GitHub](https://github.com/better-auth/better-auth) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/better-auth) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/better-auth/better-auth)
Author
Owner

@dosubot[bot] commented on GitHub (Nov 29, 2025):

The reason lastLoginMethod is always null in Expo is that the lastLoginMethodClient plugin only reads from browser cookies, which aren't available in React Native/Expo environments. Expo uses SecureStore for persistent storage, but the plugin doesn't support it out of the box—so authClient.getLastUsedLoginMethod() can't work in Expo (plugin code, docs).

Workaround:
Enable database storage for the last login method by setting storeInDatabase: true in your backend plugin config. This adds a lastLoginMethod field to your user table. On the client, access it via session.user.lastLoginMethod (using authClient.useSession() or similar), not via getLastUsedLoginMethod() (docs). This approach works across web and mobile clients.

There’s currently no built-in support for SecureStore in lastLoginMethodClient, and custom SecureStore adapters won’t solve this for the plugin—it’s strictly cookie-based. If you need per-device/session tracking (not just the most recent login), you’ll need a custom solution.

Let me know if you want a code sample for enabling database storage or have questions about session access in Expo.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Chat with Dosu Open in Cursor Join Discord Share on X

<!-- gh-comment-id:3591852527 --> @dosubot[bot] commented on GitHub (Nov 29, 2025): <!-- Answer --> The reason `lastLoginMethod` is always null in Expo is that the `lastLoginMethodClient` plugin only reads from browser cookies, which aren't available in React Native/Expo environments. Expo uses `SecureStore` for persistent storage, but the plugin doesn't support it out of the box—so `authClient.getLastUsedLoginMethod()` can't work in Expo ([plugin code](https://github.com/better-auth/better-auth/blob/7b0a6a13526e63106cea3501e026082ecb9ad3a7/packages/better-auth/src/plugins/last-login-method/client.ts#L14-L64), [docs](https://github.com/better-auth/better-auth/blob/7b0a6a13526e63106cea3501e026082ecb9ad3a7/docs/content/docs/plugins/last-login-method.mdx#L6-L301)). **Workaround:** Enable database storage for the last login method by setting `storeInDatabase: true` in your backend plugin config. This adds a `lastLoginMethod` field to your user table. On the client, access it via `session.user.lastLoginMethod` (using `authClient.useSession()` or similar), not via `getLastUsedLoginMethod()` ([docs](https://github.com/better-auth/better-auth/blob/7b0a6a13526e63106cea3501e026082ecb9ad3a7/docs/content/docs/plugins/last-login-method.mdx#L6-L301)). This approach works across web and mobile clients. There’s currently no built-in support for SecureStore in `lastLoginMethodClient`, and custom SecureStore adapters won’t solve this for the plugin—it’s strictly cookie-based. If you need per-device/session tracking (not just the most recent login), you’ll need a custom solution. Let me know if you want a code sample for enabling database storage or have questions about session access in Expo. <!-- 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/9a2f9642-ad0d-4444-b819-f4d92fb354e7?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/9a2f9642-ad0d-4444-b819-f4d92fb354e7?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/9a2f9642-ad0d-4444-b819-f4d92fb354e7?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/9a2f9642-ad0d-4444-b819-f4d92fb354e7?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/9a2f9642-ad0d-4444-b819-f4d92fb354e7?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/9a2f9642-ad0d-4444-b819-f4d92fb354e7?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/9a2f9642-ad0d-4444-b819-f4d92fb354e7?feedback_type=other)</sup> [![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=The%20reason%20%60lastLoginMethod%60%20is%20always%20null%20in%20Expo%20is%20that%20the%20%60lastLoginMethodClient%60%20plugin%20only%20reads%20from%20browser%20cookies%2C%20which%20aren%27t%20available%20in%20React%20Native/Expo%20environments.%20Expo%20uses%20%60SecureStore%60%20for%20persistent%20storage%2C%20but%20the%20plugin%20doesn%27t%20support%20it%20out%20of%20the%20box%E2%80%94so%20%60authClient.getLastUsedLoginMethod%28%29%60%20can%27t%20work%20in%20Expo%20%28%5Bplugin%20code%5D%28https%3A//github.com/better-auth/better-auth/blob/7b0a6a13526e63106cea3501e026082ecb9ad3a7/packages/better-auth/src/plugins/last-login-method/client.ts%23L14-L64%29%2C%20%5Bdocs%5D%28https%3A//github.com/better-auth/better-auth/blob/7b0a6a13526e63106cea3501e026082ecb9ad3a7/docs/content/docs/plugins/last-login-method.mdx%23L6-L301%29%29.%0A%0A%2A%2AWorkaround%3A%2A%2A%20%20%0AEnable%20database%20storage%20for%20the%20last%20login%20method%20by%20setting%20%60storeInDatabase%3A%20true%60%20in%20your%20backend%20plugin%20config.%20This%20adds%20a%20%60lastLoginMethod%60%20field%20to%20your%20user%20table.%20On%20the%20client%2C%20access%20it%20via%20%60session.user.lastLoginMethod%60%20%28using%20%60authClient.useSession%28%29%60%20or%20similar%29%2C%20not%20via%20%60getLastUsedLoginMethod%28%29%60%20%28%5Bdocs%5D%28https%3A//github.com/better-auth/better-auth/blob/7b0a6a13526e63106cea3501e026082ecb9ad3a7/docs/content/docs/plugins/last-login-method.mdx%23L6-L301%29%29.%20This%20approach%20works%20across%20web%20and%20mobile%20clients.%0A%0AThere%E2%80%99s%20currently%20no%20built-in%20support%20for%20SecureStore%20in%20%60lastLoginMethodClient%60%2C%20and%20custom%20SecureStore%20adapters%20won%E2%80%99t%20solve%20this%20for%20the%20plugin%E2%80%94it%E2%80%99s%20strictly%20cookie-based.%20If%20you%20need%20per-device/session%20tracking%20%28not%20just%20the%20most%20recent%20login%29%2C%20you%E2%80%99ll%20need%20a%20custom%20solution.%0A%0ALet%20me%20know%20if%20you%20want%20a%20code%20sample%20for%20enabling%20database%20storage%20or%20have%20questions%20about%20session%20access%20in%20Expo.)&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/6409)
Author
Owner

@jinsley8 commented on GitHub (Nov 29, 2025):

The Problem:
The better-auth lastLoginMethodClient() plugin uses browser cookies to store and retrieve the last login method. This works in web browsers but doesn't work in React Native/Expo because:

  1. The Expo auth client uses expo-secure-store for auth tokens, not browser cookies
  2. authClient.getLastUsedLoginMethod() returns null in React Native since there's no cookie to read
  3. The server-side storeInDatabase: true option saves to the database, but the client still reads from cookies

Our Workaround:
We manually track the last login method in SecureStore on the client side:

Key: appName.last_login_method
Values: "email" | "google" | "apple" | "biometric"

Implementation:

  1. Saving the last login method (after successful auth)
// In email-auth-form.tsx
import * as SecureStore from 'expo-secure-store';

const LAST_LOGIN_METHOD_KEY = 'appName.last_login_method';

// After successful email login
await SecureStore.setItemAsync(LAST_LOGIN_METHOD_KEY, 'email');

// In use-google-signin.ts
onSuccess: async (result) => {
  await SecureStore.setItemAsync(LAST_LOGIN_METHOD_KEY, 'google');
}

// In use-apple-signin.ts
onSuccess: async (result) => {
  await SecureStore.setItemAsync(LAST_LOGIN_METHOD_KEY, 'apple');
}

// In use-biometric-auth.ts
// After successful biometric sign-in
await SecureStore.setItemAsync(LAST_LOGIN_METHOD_KEY, 'biometric');
  1. Reading the last login method (on login screen)
// In login.tsx
const [lastLoginMethod, setLastLoginMethod] = useState<string | null>(null);

useEffect(() => {
  const loadLastLoginMethod = async () => {
    const method = await SecureStore.getItemAsync('appName.last_login_method');
    setLastLoginMethod(method);
  };
  void loadLastLoginMethod();
}, []);

// Then use it for UI badges
{lastLoginMethod === 'google' && <LastUsedBadge />}
{lastLoginMethod === 'email' && <LastUsedBadge />}
// etc.

Feature Request for better-auth:
The lastLoginMethodClient() plugin should support a custom storage adapter for React Native, similar to how expoClient() accepts a storage option:

lastLoginMethodClient({
  storage: SecureStore,
  storageKey: 'appName.last_login_method'
})
<!-- gh-comment-id:3591861595 --> @jinsley8 commented on GitHub (Nov 29, 2025): **The Problem:** The better-auth `lastLoginMethodClient()` plugin uses browser cookies to store and retrieve the last login method. This works in web browsers but doesn't work in React Native/Expo because: 1. The Expo auth client uses `expo-secure-store` for auth tokens, not browser cookies 2. `authClient.getLastUsedLoginMethod()` returns `null` in React Native since there's no cookie to read 3. The server-side `storeInDatabase: true` option saves to the database, but the client still reads from cookies **Our Workaround:** We manually track the last login method in SecureStore on the client side: Key: `appName.last_login_method` Values: `"email" | "google" | "apple" | "biometric"` **Implementation:** 1. Saving the last login method (after successful auth) ```js // In email-auth-form.tsx import * as SecureStore from 'expo-secure-store'; const LAST_LOGIN_METHOD_KEY = 'appName.last_login_method'; // After successful email login await SecureStore.setItemAsync(LAST_LOGIN_METHOD_KEY, 'email'); // In use-google-signin.ts onSuccess: async (result) => { await SecureStore.setItemAsync(LAST_LOGIN_METHOD_KEY, 'google'); } // In use-apple-signin.ts onSuccess: async (result) => { await SecureStore.setItemAsync(LAST_LOGIN_METHOD_KEY, 'apple'); } // In use-biometric-auth.ts // After successful biometric sign-in await SecureStore.setItemAsync(LAST_LOGIN_METHOD_KEY, 'biometric'); ``` 2. Reading the last login method (on login screen) ```js // In login.tsx const [lastLoginMethod, setLastLoginMethod] = useState<string | null>(null); useEffect(() => { const loadLastLoginMethod = async () => { const method = await SecureStore.getItemAsync('appName.last_login_method'); setLastLoginMethod(method); }; void loadLastLoginMethod(); }, []); // Then use it for UI badges {lastLoginMethod === 'google' && <LastUsedBadge />} {lastLoginMethod === 'email' && <LastUsedBadge />} // etc. ``` **Feature Request for better-auth:** The lastLoginMethodClient() plugin should support a custom storage adapter for React Native, similar to how expoClient() accepts a storage option: ```js lastLoginMethodClient({ storage: SecureStore, storageKey: 'appName.last_login_method' }) ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#27831