最后登录方法
最后登录方法插件跟踪用户使用的最近认证方法(电子邮件、OAuth 提供商等)。这使您能够在登录页面上显示有用的指示,例如“上次使用 Google 登录”或根据用户偏好优先考虑某些登录方法。
安装
将插件添加到您的认证配置中
import { betterAuth } from "better-auth"
import { lastLoginMethod } from "better-auth/plugins"
export const auth = betterAuth({
// ... other config options
plugins: [
lastLoginMethod()
]
})将客户端插件添加到您的认证客户端中
import { createAuthClient } from "better-auth/client"
import { lastLoginMethodClient } from "better-auth/client/plugins"
export const authClient = createAuthClient({
plugins: [
lastLoginMethodClient()
]
})使用
安装后,插件会自动跟踪用户使用的最后认证方法。然后,您可以在应用程序中检索并显示此信息。
获取最后使用的认证方法
客户端插件提供了几个方法来处理最后登录方法:
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 集成示例
以下是如何使用插件来增强您的登录页面的示例:
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:
import { betterAuth } from "better-auth"
import { lastLoginMethod } from "better-auth/plugins"
export const auth = betterAuth({
plugins: [
lastLoginMethod({
storeInDatabase: true
})
]
})运行数据库迁移
插件会自动将 lastLoginMethod 字段添加到您的用户表中。运行迁移以应用更改:
npx @better-auth/cli migratenpx @better-auth/cli generate访问数据库字段
启用数据库存储后,lastLoginMethod 字段将在用户对象中可用:
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 Name | Type | Key | Description |
|---|---|---|---|
| lastLoginMethod | string | 用户使用的最后认证方法 |
自定义架构配置
您可以自定义数据库字段名称:
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
}
}
})
]
})配置选项
最后登录方法插件接受以下选项:
服务器选项
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字段映射到自定义列名称
客户端选项
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.com→app.example.com - 跨源设置:
api.company.com→app.different.com
当您在 Better Auth 配置中启用 crossSubDomainCookies 或 crossOriginCookies 时,插件会自动使用与会话 cookie 相同的域、secure 和 sameSite 设置,确保在整个应用程序中行为一致。
高级示例
自定义提供商跟踪
如果您有自定义 OAuth 提供商或认证方法,可以使用 customResolveMethod 选项:
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
}
})
]
})