docs/content/docs/plugins/last-login-method.mdx
The last login method plugin tracks the most recent authentication method used by users (email, OAuth providers, etc.). This enables you to display helpful indicators on login pages, such as "Last signed in with Google" or prioritize certain login methods based on user preferences.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { lastLoginMethod } from "better-auth/plugins" // [!code highlight]
export const auth = betterAuth({
// ... other config options
plugins: [
lastLoginMethod() // [!code highlight]
]
})
```
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
import { lastLoginMethodClient } from "better-auth/client/plugins" // [!code highlight]
export const authClient = createAuthClient({
plugins: [
lastLoginMethodClient() // [!code highlight]
]
})
```
Once installed, the plugin automatically tracks the last authentication method used by users. You can then retrieve and display this information in your application.
The client plugin provides several methods to work with the last login method:
import { authClient } from "@/lib/auth-client"
// Get the last used login method
const lastMethod = authClient.getLastUsedLoginMethod()
console.log(lastMethod) // "google", "email", "github", etc.
// Check if a specific method was last used
const wasGoogle = authClient.isLastUsedLoginMethod("google")
// Clear the stored method
authClient.clearLastUsedLoginMethod()
Here's how to use the plugin to enhance your login page:
import { authClient } from "@/lib/auth-client"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
export function SignInPage() {
const lastMethod = authClient.getLastUsedLoginMethod()
return (
<div className="space-y-4">
<h1>Sign In</h1>
<div className="relative">
<Button
onClick={() => authClient.signIn.email({...})}
variant={lastMethod === "email" ? "default" : "outline"}
className="w-full"
>
Sign in with Email
{lastMethod === "email" && (
<Badge className="ml-2">Last used</Badge>
)}
</Button>
</div>
<div className="relative">
<Button
onClick={() => authClient.signIn.social({ provider: "google" })}
variant={lastMethod === "google" ? "default" : "outline"}
className="w-full"
>
Continue with Google
{lastMethod === "google" && (
<Badge className="ml-2">Last used</Badge>
)}
</Button>
</div>
<div className="relative">
<Button
onClick={() => authClient.signIn.social({ provider: "github" })}
variant={lastMethod === "github" ? "default" : "outline"}
className="w-full"
>
Continue with GitHub
{lastMethod === "github" && (
<Badge className="ml-2">Last used</Badge>
)}
</Button>
</div>
</div>
)
}
By default, the last login method is stored only in cookies. For more persistent tracking and analytics, you can enable database storage.
<Steps> <Step> ### Enable database storageSet `storeInDatabase` to `true` in your plugin configuration:
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { lastLoginMethod } from "better-auth/plugins"
export const auth = betterAuth({
plugins: [
lastLoginMethod({
storeInDatabase: true // [!code highlight]
})
]
})
```
The plugin will automatically add a `lastLoginMethod` field to your user table. Run the migration to apply the changes:
<Tabs items={["migrate", "generate"]}>
<Tab value="migrate">
```package-install
npx auth@latest migrate
```
</Tab>
<Tab value="generate">
```package-install
npx auth@latest generate
```
</Tab>
</Tabs>
When database storage is enabled, the `lastLoginMethod` field becomes available in user objects:
```ts title="user-profile.tsx"
import { auth } from "@/lib/auth"
// Server-side access
const session = await auth.api.getSession({ headers })
console.log(session?.user.lastLoginMethod) // "google", "email", etc.
// Client-side access via session
const { data: session } = authClient.useSession()
console.log(session?.user.lastLoginMethod)
```
When storeInDatabase is enabled, the plugin adds the following field to the user table:
Table: user
export const lastLoginMethodUserTableFields = [ { name: "lastLoginMethod", type: "string", description: "The last authentication method used by the user", isOptional: true, }, ];
<DatabaseTable name="user" fields={lastLoginMethodUserTableFields} />You can customize the database field name:
import { betterAuth } from "better-auth"
import { lastLoginMethod } from "better-auth/plugins"
export const auth = betterAuth({
plugins: [
lastLoginMethod({
storeInDatabase: true,
schema: {
user: {
lastLoginMethod: "last_auth_method" // Custom field name
}
}
})
]
})
The last login method plugin accepts the following options:
import { betterAuth } from "better-auth"
import { lastLoginMethod } from "better-auth/plugins"
export const auth = betterAuth({
plugins: [
lastLoginMethod({
// Cookie configuration
cookieName: "better-auth.last_used_login_method", // Default: "better-auth.last_used_login_method"
maxAge: 60 * 60 * 24 * 30, // Default: 30 days in seconds
// Database persistence
storeInDatabase: false, // Default: false
// Custom method resolution
customResolveMethod: (ctx) => {
// Custom logic to determine the login method
if (ctx.path === "/oauth/callback/custom-provider") {
return "custom-provider"
}
// Return null to use default resolution
return null
},
// GDPR compliance hook
beforeStoreCookie: async (ctx, lastUsedLoginMethod) => {
// Check if user has given consent for non-essential cookies
// Return false to prevent cookie storage
const hasConsent = await checkUserCookieConsent(ctx)
return hasConsent
},
// Schema customization (when storeInDatabase is true)
schema: {
user: {
lastLoginMethod: "custom_field_name"
}
}
})
]
})
cookieName: string
"better-auth.last_used_login_method"httpOnly: false to allow client-side JavaScript access for UI featuresmaxAge: number
2592000 (30 days)storeInDatabase: boolean
falselastLoginMethod field to the user tablebeforeStoreCookie: () => false:lastLoginMethod({
storeInDatabase: true,
beforeStoreCookie: () => false, // never set the non-essential cookie
})
customResolveMethod: (ctx: GenericEndpointContext) => string | null
null to use the default resolution logicbeforeStoreCookie: (ctx: GenericEndpointContext, lastUsedLoginMethod: string) => Promise<boolean> | boolean
true to allow the cookie to be set, false to prevent itimport { betterAuth } from "better-auth"
import { lastLoginMethod } from "better-auth/plugins"
export const auth = betterAuth({
plugins: [
lastLoginMethod({
beforeStoreCookie: async (ctx, lastUsedLoginMethod) => {
// Check if user has given consent for non-essential cookies
// This is important for GDPR compliance
const hasConsent = await checkUserCookieConsent(ctx)
return hasConsent
}
})
]
})
schema: object
storeInDatabase is enabledlastLoginMethod field to a custom column nameimport { createAuthClient } from "better-auth/client"
import { lastLoginMethodClient } from "better-auth/client/plugins"
export const authClient = createAuthClient({
plugins: [
lastLoginMethodClient({
cookieName: "better-auth.last_used_login_method", // Default: "better-auth.last_used_login_method"
domain: ".example.com" // Required for cross-subdomain cookie clearing
})
]
})
cookieName: string
cookieName configuration"better-auth.last_used_login_method"domain: string
crossSubDomainCookies so the client can properly expire the cookie set by the serverdomain value in your server's crossSubDomainCookies configurationBy default, the plugin tracks these authentication methods:
"email""google", "github", "discord")The plugin automatically detects the method from these endpoints:
/callback/:id - OAuth callback with provider ID/sign-in/email - Email sign in/sign-up/email - Email sign upThe plugin automatically inherits cookie settings from Better Auth's centralized cookie system. This solves the problem where the last login method wouldn't persist across:
auth.example.com → app.example.comapi.company.com → app.different.comWhen you enable crossSubDomainCookies or crossOriginCookies in your Better Auth config, the plugin will automatically use the same domain, secure, and sameSite settings as your session cookies, ensuring consistent behavior across your application.
import { createAuthClient } from "better-auth/client"
import { lastLoginMethodClient } from "better-auth/client/plugins"
export const authClient = createAuthClient({
plugins: [
lastLoginMethodClient({
domain: ".example.com" // Must match server crossSubDomainCookies domain // [!code highlight]
})
]
})
The last login method cookie is considered a non-essential cookie under GDPR regulations. To comply with GDPR and similar privacy laws, you should only store this cookie if the user has given explicit consent.
The beforeStoreCookie hook allows you to implement consent checks before storing the cookie:
import { betterAuth } from "better-auth"
import { lastLoginMethod } from "better-auth/plugins"
export const auth = betterAuth({
plugins: [
lastLoginMethod({
beforeStoreCookie: async (ctx, lastUsedLoginMethod) => {
// Example 1: Check consent from session or database
const session = await getSessionFromCtx(ctx)
if (session?.user) {
// custom function which hits your database to check if the user has given consent
const userConsent = await checkUserConsent(session.user.id)
return userConsent?.allowsNonEssentialCookies ?? false
}
// Example 2: Check consent from request headers (cookie banner)
// parseConsentCookie should return false/null/undefined when no consent is present
const consentCookie = ctx.request?.headers?.get("cookie")
const hasConsent = parseConsentCookie(consentCookie)
return !!hasConsent
}
})
]
})
If you have custom OAuth providers or authentication methods, you can use the customResolveMethod option:
import { betterAuth } from "better-auth"
import { lastLoginMethod } from "better-auth/plugins"
export const auth = betterAuth({
plugins: [
lastLoginMethod({
customResolveMethod: (ctx) => {
// Track custom SAML provider
if (ctx.path === "/saml/callback") {
return "saml"
}
// Track magic link authentication
if (ctx.path === "/magic-link/verify") {
return "magic-link"
}
// Track phone authentication
if (ctx.path === "/sign-in/phone") {
return "phone"
}
// Return null to use default logic
return null
}
})
]
})
When using Better Auth with Expo, make sure to import the client plugin from @better-auth/expo/plugins rather than from better-auth/plugins/client. This ensures the last login method is stored correctly using the configured storage.
import { createAuthClient } from "better-auth/react"
import { expoClient } from "@better-auth/expo"
import { lastLoginMethodClient } from "@better-auth/expo/plugins" // [!code highlight]
import * as SecureStore from "expo-secure-store"
export const authClient = createAuthClient({
plugins: [
expoClient({
scheme: "myapp",
storagePrefix: "myapp",
storage: SecureStore,
}),
lastLoginMethodClient({
storagePrefix: "myapp",
storage: SecureStorage,
})
]
})