上次登录方式

跟踪并显示用户最后使用的身份验证方式

上次登录方式插件用于跟踪用户最近一次使用的身份验证方式(邮箱、OAuth 提供商等)。这使您能够在登录页面上显示有用的指示标志,例如“上次使用 Google 登录”,或根据用户偏好优先显示某些登录方式。

安装

在你的 auth 配置中添加插件

auth.ts
import { betterAuth } from "better-auth"
import { lastLoginMethod } from "better-auth/plugins"

export const auth = betterAuth({
    // ... 其他配置选项
    plugins: [
        lastLoginMethod() 
    ]
})

在你的 auth 客户端中添加客户端插件

auth-client.ts
import { createAuthClient } from "better-auth/client"
import { lastLoginMethodClient } from "better-auth/client/plugins"

export const authClient = createAuthClient({
    plugins: [
        lastLoginMethodClient() 
    ]
})

使用方法

安装后,插件会自动跟踪用户最后一次使用的身份验证方式。你可以在应用程序中检索并显示该信息。

获取最后使用的方式

客户端插件提供了多个方法来操作最后登录的方式:

app.tsx
import { authClient } from "@/lib/auth-client"

// 获取最后使用的登录方式
const lastMethod = authClient.getLastUsedLoginMethod()
console.log(lastMethod) // "google", "email", "github" 等等

// 检查某种方式是否为最后使用
const wasGoogle = authClient.isLastUsedLoginMethod("google")

// 清除存储的登录方式
authClient.clearLastUsedLoginMethod()

UI 集成示例

下面展示如何使用该插件增强登录页面:

sign-in.tsx
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>登录</h1>
            
            {/* 邮箱登录 */}
            <div className="relative">
                <Button 
                    onClick={() => authClient.signIn.email({...})}
                    variant={lastMethod === "email" ? "default" : "outline"}
                    className="w-full"
                >
                    使用邮箱登录
                    {lastMethod === "email" && (
                        <Badge className="ml-2">上次使用</Badge>
                    )}
                </Button>
            </div>
            
            {/* OAuth 提供商 */}
            <div className="relative">
                <Button 
                    onClick={() => authClient.signIn.social({ provider: "google" })}
                    variant={lastMethod === "google" ? "default" : "outline"}
                    className="w-full"
                >
                    使用 Google 登录
                    {lastMethod === "google" && (
                        <Badge className="ml-2">上次使用</Badge>
                    )}
                </Button>
            </div>
            
            <div className="relative">
                <Button 
                    onClick={() => authClient.signIn.social({ provider: "github" })}
                    variant={lastMethod === "github" ? "default" : "outline"}
                    className="w-full"
                >
                    使用 GitHub 登录
                    {lastMethod === "github" && (
                        <Badge className="ml-2">上次使用</Badge>
                    )}
                </Button>
            </div>
        </div>
    )
}

数据库持久化

默认情况下,最后登录方式仅存储在 cookie 中。若需更持久的跟踪和分析,可以启用数据库存储。

启用数据库存储

在插件配置中将 storeInDatabase 设置为 true

auth.ts
import { betterAuth } from "better-auth"
import { lastLoginMethod } from "better-auth/plugins"

export const auth = betterAuth({
    plugins: [
        lastLoginMethod({
            storeInDatabase: true
        })
    ]
})

执行数据库迁移

插件会自动向用户表添加 lastLoginMethod 字段。运行迁移以应用变更:

npx auth@latest migrate
npx auth@latest generate

访问数据库字段

启用数据库存储后,lastLoginMethod 字段将出现在用户对象中:

user-profile.tsx
import { auth } from "@/lib/auth"

// 服务器端访问
const session = await auth.api.getSession({ headers })
console.log(session?.user.lastLoginMethod) // "google", "email" 等等

// 客户端通过 session 访问
const { data: session } = authClient.useSession()
console.log(session?.user.lastLoginMethod)

数据库模式

启用 storeInDatabase 后,插件会在 user 表中添加如下字段:

表:user

Table
字段
类型
描述
lastLoginMethod
string
?
用户最后使用的身份验证方式

自定义模式配置

你可以自定义数据库字段名:

auth.ts
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" // 自定义字段名
                }
            }
        })
    ]
})

配置选项

上次登录方式插件接受以下选项:

服务端选项

auth.ts
import { betterAuth } from "better-auth"
import { lastLoginMethod } from "better-auth/plugins"

export const auth = betterAuth({
    plugins: [
        lastLoginMethod({
            // Cookie 配置
            cookieName: "better-auth.last_used_login_method", // 默认: "better-auth.last_used_login_method"
            maxAge: 60 * 60 * 24 * 30, // 默认: 30 天,单位秒
            
            // 数据库存储
            storeInDatabase: false, // 默认: false
            
            // 自定义方式解析
            customResolveMethod: (ctx) => {
                // 自定义逻辑决定登录方式
                if (ctx.path === "/oauth/callback/custom-provider") {
                    return "custom-provider"
                }
                // 返回 null 使用默认解析
                return null
            },
            
            // 模式定制 (启用 storeInDatabase 时)
            schema: {
                user: {
                    lastLoginMethod: "custom_field_name"
                }
            }
        })
    ]
})

cookieName: string

  • 用于存储最后登录方式的 cookie 名称
  • 默认: "better-auth.last_used_login_method"
  • 注意:该 cookie 设置为 httpOnly: false,允许客户端 JavaScript 访问,用于 UI 功能

maxAge: number

  • cookie 过期时间,单位为秒
  • 默认: 2592000(30 天)

storeInDatabase: boolean

  • 是否将最后登录方式存储到数据库中
  • 默认: false
  • 启用后会在用户表中添加 lastLoginMethod 字段

customResolveMethod: (ctx: GenericEndpointContext) => string | null

  • 自定义函数根据请求上下文判断登录方式
  • 返回 null 则使用默认解析逻辑
  • 适用于自定义 OAuth 提供商或身份验证流程

schema: object

  • 在启用 storeInDatabase 时自定义数据库字段名映射
  • 允许将 lastLoginMethod 字段映射到自定义列名

客户端选项

auth-client.ts
import { 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" // 默认: "better-auth.last_used_login_method"
        })
    ]
})

cookieName: string

  • 用于读取最后登录方式的 cookie 名称
  • 必须和服务端的 cookieName 配置一致
  • 默认: "better-auth.last_used_login_method"

默认方式解析

插件默认跟踪以下身份验证方式:

  • 邮箱身份验证"email"
  • OAuth 提供商:提供商 ID(例如 "google", "github", "discord"
  • OAuth2 回调:从 URL 路径提取提供商 ID
  • 注册方式:和登录方式相同的方式跟踪

插件自动检测以下端点的方式:

  • /callback/:id - OAuth 回调,带有提供商 ID
  • /oauth2/callback/:id - OAuth2 回调,带有提供商 ID
  • /sign-in/email - 邮箱登录
  • /sign-up/email - 邮箱注册

跨域支持

插件自动继承 Better Auth 集中 cookie 系统的 cookie 设置,解决了最后登录方式无法跨越以下情况持久化的问题:

  • 跨子域部署auth.example.comapp.example.com
  • 跨源部署api.company.comapp.different.com

当你在 Better Auth 配置中启用 crossSubDomainCookiescrossOriginCookies,插件会自动使用与会话 cookie 相同的域、secure 和 sameSite 设置,确保应用中的一致行为。

高级示例

自定义提供商跟踪

如果你有自定义的 OAuth 提供商或身份验证方式,可以使用 customResolveMethod 选项:

auth.ts
import { betterAuth } from "better-auth"
import { lastLoginMethod } from "better-auth/plugins"

export const auth = betterAuth({
    plugins: [
        lastLoginMethod({
            customResolveMethod: (ctx) => {
                // 跟踪自定义 SAML 提供商
                if (ctx.path === "/saml/callback") {
                    return "saml"
                }
                
                // 跟踪 magic link 验证
                if (ctx.path === "/magic-link/verify") {
                    return "magic-link"
                }
                
                // 跟踪手机身份验证
                if (ctx.path === "/sign-in/phone") {
                    return "phone"
                }
                
                // 返回 null 使用默认逻辑
                return null
            }
        })
    ]
})

与 Expo 一起使用

使用 Better Auth 配合 Expo 时,请确保从 @better-auth/expo/plugins 而非 better-auth/plugins/client 导入客户端插件。这确保最后登录方式能正确存储到配置的存储中。

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

export const authClient = createAuthClient({
  plugins: [
    expoClient({
      scheme: "myapp",
      storagePrefix: "myapp",
      storage: SecureStore,
    }),
    lastLoginMethodClient({
      storagePrefix: "myapp",
      storage: SecureStorage,
    })
  ]
})

对于仅使用 Expo 的应用,不需要浏览器支持时,可以只使用客户端插件,省略服务端插件。