最后登录方法

最后登录方法插件跟踪用户使用的最近认证方法(电子邮件、OAuth 提供商等)。这使您能够在登录页面上显示有用的指示,例如“上次使用 Google 登录”或根据用户偏好优先考虑某些登录方法。

安装

将插件添加到您的认证配置中

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

export const auth = betterAuth({
    // ... other config options
    plugins: [
        lastLoginMethod() 
    ]
})

将客户端插件添加到您的认证客户端中

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"

// 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()

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>
            
            {/* Email sign in */}
            <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 providers */}
            <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 @better-auth/cli migrate
npx @better-auth/cli generate

访问数据库字段

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

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)

数据库架构

启用 storeInDatabase 时,插件会将以下字段添加到 user 表中:

表:user

Field NameTypeKeyDescription
lastLoginMethodstring用户使用的最后认证方法

自定义架构配置

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

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" // Custom field name
                }
            }
        })
    ]
})

配置选项

最后登录方法插件接受以下选项:

服务器选项

auth.ts
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
            },
            
            // Schema customization (when storeInDatabase is true)
            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" // Default: "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 - 带有提供商 ID 的 OAuth 回调
  • /oauth2/callback/:id - 带有提供商 ID 的 OAuth2 回调
  • /sign-in/email - 电子邮件登录
  • /sign-up/email - 电子邮件注册

跨域支持

插件会自动继承 Better Auth 的集中式 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) => {
                // Track custom SAML provider
                if (ctx.path === "/saml/callback") {
                    return "saml"
                }
                
                // Track magic link authentication
                if (ctx.path === "/verify-magic-link") {
                    return "magic-link"
                }
                
                // Track phone authentication
                if (ctx.path === "/sign-in/phone") {
                    return "phone"
                }
                
                // Return null to use default logic
                return null
            }
        })
    ]
})