会话管理

Better Auth 使用传统的基于 Cookie 的会话管理来管理会话。会话存储在 Cookie 中,并在每次请求时发送到服务器。然后,服务器验证会话,如果会话有效,则返回用户数据。

【Better Auth manages session using a traditional cookie-based session management. The session is stored in a cookie and is sent to the server on every request. The server then verifies the session and returns the user data if the session is valid.】

会话表

【Session table】

会话表存储会话数据。会话表包含以下字段:

【The session table stores the session data. The session table has the following fields:】

  • id:会话的唯一标识符。
  • token:会话令牌,也用作会话 Cookie。
  • userId:用户的用户ID。
  • expiresAt:会话的过期日期。
  • ipAddress:用户的 IP 地址。
  • userAgent:用户的用户代理。它存储来自请求的用户代理头。

会话过期

【Session Expiration】

默认情况下,会话在 7 天后过期。但每当会话被使用并且达到 updateAge 时,会话过期时间会更新为当前时间加上 expiresIn 的值。

【The session expires after 7 days by default. But whenever the session is used and the updateAge is reached, the session expiration is updated to the current time plus the expiresIn value.】

你可以通过将 session 对象传递给 auth 配置来更改 expiresInupdateAge 的值。

【You can change both the expiresIn and updateAge values by passing the session object to the auth configuration.】

auth.ts
import { betterAuth } from "better-auth"

export const auth = betterAuth({
    //... other config options
    session: {
        expiresIn: 60 * 60 * 24 * 7, // 7 days
        updateAge: 60 * 60 * 24 // 1 day (every 1 day the session expiration is updated)
    }
})

禁用会话刷新

【Disable Session Refresh】

你可以禁用会话刷新,这样无论 updateAge 选项如何,都会话都不会被更新。

【You can disable session refresh so that the session is not updated regardless of the updateAge option.】

auth.ts
import { betterAuth } from "better-auth"

export const auth = betterAuth({
    //... other config options
    session: {
        disableSessionRefresh: true
    }
})

会话新鲜度

【Session Freshness】

Better Auth 中的一些端点要求会话必须是新鲜的。如果会话的 createdAtfreshAge 限制范围内,则该会话被视为新鲜。默认情况下,freshAge 设置为1 天(60 * 60 * 24)。

【Some endpoints in Better Auth require the session to be fresh. A session is considered fresh if its createdAt is within the freshAge limit. By default, the freshAge is set to 1 day (60 * 60 * 24). 】

你可以通过在 auth 配置中传入 session 对象来自定义 freshAge 值:

【You can customize the freshAge value by passing a session object in the auth configuration: 】

auth.ts
import { betterAuth } from "better-auth"

export const auth = betterAuth({
    //... other config options
    session: {
        freshAge: 60 * 5 // 5 minutes (the session is fresh if created within the last 5 minutes)
    }
})

禁用新鲜度检查,将 freshAge 设置为 0:

【To disable the freshness check, set freshAge to 0: 】

auth.ts
import { betterAuth } from "better-auth"

export const auth = betterAuth({
    //... other config options
    session: {
        freshAge: 0 // Disable freshness check
    }
})

会话管理

【Session Management】

Better Auth 提供了一套用于管理会话的功能。

【Better Auth provides a set of functions to manage sessions.】

获取会话

【Get Session】

getSession 函数用于获取当前活动会话。

【The getSession function retrieves the current active session.】

import { authClient } from "@/lib/client"

const { data: session } = await authClient.getSession()

要了解如何自定义会话响应,请查看自定义会话响应部分。

【To learn how to customize the session response check the Customizing Session Response section.】

使用会话

【Use Session】

useSession 操作提供了一种响应式方式来访问当前会话。

【The useSession action provides a reactive way to access the current session.】

import { authClient } from "@/lib/client"

const { data: session } = authClient.useSession()

会话列表

【List Sessions】

listSessions 函数返回用户当前活跃的会话列表。

【The listSessions function returns a list of sessions that are active for the user.】

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

const sessions = await authClient.listSessions()

撤销会话

【Revoke Session】

当用户从设备登出时,会话会自动结束。但你也可以在用户登录的任何设备上手动结束会话。

【When a user signs out of a device, the session is automatically ended. However, you can also end a session manually from any device the user is signed into.】

要结束会话,请使用 revokeSession 函数。只需将会话令牌作为参数传递即可。

【To end a session, use the revokeSession function. Just pass the session token as a parameter.】

auth-client.ts
await authClient.revokeSession({
    token: "session-token"
})

撤销其他会话

【Revoke Other Sessions】

要撤销除当前会话之外的所有其他会话,你可以使用 revokeOtherSessions 函数。

【To revoke all other sessions except the current session, you can use the revokeOtherSessions function.】

auth-client.ts
await authClient.revokeOtherSessions()

撤销所有会话

【Revoke All Sessions】

要撤销所有会话,你可以使用 revokeSessions 函数。

【To revoke all sessions, you can use the revokeSessions function.】

auth-client.ts
await authClient.revokeSessions()

更改密码时撤销会话

【Revoking Sessions on Password Change】

当用户更改密码时,你可以通过在 changePassword 函数中将 revokeOtherSessions 设置为 true 来撤销所有会话。

【You can revoke all sessions when the user changes their password by passing revokeOtherSessions as true on changePassword function.】

auth.ts
await authClient.changePassword({
    newPassword: newPassword,
    currentPassword: currentPassword,
    revokeOtherSessions: true,
})

会话缓存

【Session Caching】

【Cookie Cache】

每次调用 useSessiongetSession 时访问数据库并不理想,尤其是当会话不经常改变的时候。Cookie 缓存通过将会话数据存储在一个短期有效、已签名的 cookie 中来处理这个问题 - 类似于使用刷新令牌的 JWT 访问令牌。

【Calling your database every time useSession or getSession is invoked isn't ideal, especially if sessions don't change frequently. Cookie caching handles this by storing session data in a short-lived, signed cookie—similar to how JWT access tokens are used with refresh tokens.】

启用 Cookie 缓存后,服务器可以直接从 Cookie 检查会话的有效性,而无需每次都访问数据库。Cookie 会进行签名以防篡改,短周期的 maxAge 确保会话数据能够定期刷新。如果会话被撤销或过期,Cookie 会自动失效。

【When cookie caching is enabled, the server can check session validity from the cookie itself instead of hitting the database each time. The cookie is signed to prevent tampering, and a short maxAge ensures that the session data gets refreshed regularly. If a session is revoked or expires, the cookie will be invalidated automatically.】

要启用 Cookie 缓存,只需在你的身份验证配置中设置 session.cookieCache

【To turn on cookie caching, just set session.cookieCache in your auth config:】

auth.ts
import { betterAuth } from "better-auth"

export const auth = betterAuth({
    session: {
        cookieCache: {
            enabled: true,
            maxAge: 5 * 60 // Cache duration in seconds (5 minutes)
        }
    }
});

注意事项

当启用 cookieCache 时,被撤销的会话可能会在其他设备上保持活跃状态,直到 cookie 缓存过期(maxAge)。这是因为:

【When cookieCache is enabled, revoked sessions may remain active on other devices until the cookie cache expires (maxAge). This is because:】

  • Cookie 缓存将会话数据存储在客户端浏览器中
  • 服务器无法直接删除其他设备上的 cookies
  • 只有在缓存过期或使用 disableCookieCache: true 时,才会重新验证会话

如果立即撤销会话至关重要:

  • 完全禁用 cookieCache,或者
  • 设置较短的 maxAge(例如 60 秒),或者
  • 对敏感操作使用 disableCookieCache: true

【Cookie Cache Strategies】

Better Auth 支持三种不同的 cookie 缓存编码策略:

【Better Auth supports three different encoding strategies for cookie cache:】

  • compact(默认):使用带 HMAC-SHA256 签名的 base64url 编码。最紧凑的格式,没有 JWT 规范的额外开销。性能和大小最佳。
  • jwt:使用 HMAC-SHA256 签名(HS256)的标准 JWT。已签名但未加密——任何人都可以阅读,但无法篡改。遵循 JWT 规范以实现互操作性。
  • jwe:使用 JWE(JSON Web Encryption),采用 A256CBC-HS512 和 HKDF 密钥派生。完全加密的令牌——既不可读取也不可篡改。最安全,但体积最大。

比较:

策略大小安全性可读性可互操作性使用案例
compact最小良好(签名)对性能关键,内部使用
jwt中等良好(签名)需要 JWT 兼容性,外部集成
jwe最大最佳(加密)敏感数据,最高安全性
auth.ts
export const auth = betterAuth({
    session: {
        cookieCache: {
            enabled: true,
            maxAge: 5 * 60,
            strategy: "compact" // or "jwt" or "jwe"
        }
    }
});

注意: 所有策略都是加密安全的,且能够防止篡改。主要区别在于大小、可读性以及与 JWT 规范的兼容性。

何时使用每种情况:

  • 使用 compact 当你需要最高性能和最小的 Cookie 大小时。最适合大多数仅在 Better Auth 内部使用 Cookie 的应用。
  • 使用 jwt 当你需要与外部系统兼容的 JWT,或者当你想要可以被第三方工具验证的标准 JWT 令牌时。令牌是可读的(Base64 编码的 JSON),但防篡改。
  • 使用 jwe 当你需要最高级别的安全性并希望对客户端隐藏会话数据时。令牌是完全加密的,没有密钥无法读取。将其用于敏感数据或合规要求。

如果你想在获取会话时禁用从 Cookie 缓存返回,可以传递 disableCookieCache:true,这将强制服务器从数据库获取会话,并同时刷新 Cookie 缓存。

【If you want to disable returning from the cookie cache when fetching the session, you can pass disableCookieCache:true this will force the server to fetch the session from the database and also refresh the cookie cache.】

auth-client.ts
const session = await authClient.getSession({ query: {
    disableCookieCache: true
}})

或者在服务器上

【or on the server】

server.ts
await auth.api.getSession({
    query: {
        disableCookieCache: true,
    }, 
    headers: req.headers, // pass the headers
});

二级存储中的会话

【Sessions in Secondary Storage】

默认情况下,如果你在认证配置中提供了二级存储,会话将存储在二级存储中。

【By default, if you provide a secondary storage in your auth configuration, the session will be stored in the secondary storage.】

betterAuth({
  // ... other options
  secondaryStorage: {
    // Your implementation here
  },
});

在数据库中存储会话

【Storing Sessions in the Database】

默认情况下,Better Auth 已经将会话存储在数据库中,但是如果你提供了辅助存储,Better Auth 将会把会话存储在辅助存储中,而不是数据库里。

【By default, Better Auth already stores sessions in the database, however if you provide a secondary storage, Better Auth will store sessions in the secondary storage instead of the database.】

你可以通过在会话配置中传递 storeSessionInDatabase: true 来选择将会话存储在数据库中,而不是二级存储。

【You can choose to store sessions in the database instead of secondary storage by passing storeSessionInDatabase: true in the session configuration.】

auth.ts
export const auth = betterAuth({
    secondaryStorage: { /** your secondary storage implementation here */ },
    session: { 
        storeSessionInDatabase: true, 
    } 
});

保持会话

【Preserving Sessions】

当会话被撤销时,它将从二级存储中移除,然而如果你启用 preserveSessionInDatabase,会话将保存在数据库中,不会被删除。

【When a session is revoked, it will be removed from the secondary storage, however if you enable preserveSessionInDatabase, the session will be preserved in the database and not be deleted.】

如果你想跟踪已被撤销的会话,这会很有用。

【This is useful if you want to keep track of the sessions that have been revoked.】

auth.ts
export const auth = betterAuth({
    secondaryStorage: { /** your secondary storage implementation here */ },
    session: { 
        preserveSessionInDatabase: true, 
    } 
});

无状态会话管理

【Stateless Session Management】

Better Auth 支持无状态会话管理,无需任何数据库。这意味着会话数据存储在签名/加密的 Cookie 中,服务器从不查询数据库来验证会话 - 它只需验证 Cookie 的签名并检查其是否过期。

【Better Auth supports stateless session management without any database. This means that the session data is stored in a signed/encrypted cookie and the server never queries a database to validate sessions - it simply verifies the cookie signature and checks expiration.】

基本无状态设置

【Basic Stateless Setup】

如果你没有提供数据库配置,Better Auth 将自动启用无状态模式。

【If you don't pass a database configuration, Better Auth will automatically enable stateless mode.】

auth.ts
import { betterAuth } from "better-auth"

export const auth = betterAuth({
    // No database configuration
    socialProviders: {
        google: {
            clientId: process.env.GOOGLE_CLIENT_ID,
            clientSecret: process.env.GOOGLE_CLIENT_SECRET,
        },
    },
});

要手动启用无状态模式,你需要使用以下选项配置 cookieCacheaccount

【To manually enable stateless mode, you need to configure cookieCache and account with the following options:】

auth.ts
import { betterAuth } from "better-auth"

export const auth = betterAuth({
    session: {
        cookieCache: {
            enabled: true,
            maxAge: 7 * 24 * 60 * 60, // 7 days cache duration
            strategy: "jwe", // can be "jwt" or "compact"
            refreshCache: true, // Enable stateless refresh
        },
    },
    account: {
        storeStateStrategy: "cookie",
        storeAccountCookie: true, // Store account data after OAuth flow in a cookie (useful for database-less flows)
    }
});

如果你不提供数据库,我们默认会为你提供上述配置。

理解 refreshCache

【Understanding refreshCache

refreshCache 选项控制自动在过期前刷新 cookie,而无需查询任何数据库:

【The refreshCache option controls automatic cookie refresh before expiry without querying any database:】

  • false(默认):不自动刷新。当 cookie 缓存过期(达到 maxAge)时,如果可用,它将尝试从数据库中获取。
  • true:使用默认设置启用自动刷新。当达到 maxAge 的 80%(剩余时间 20%)时刷新。
  • object:带有 updateAge 属性的自定义刷新配置。
auth.ts
export const auth = betterAuth({
    session: {
        cookieCache: {
            enabled: true,
            maxAge: 300, // 5 minutes
            refreshCache: {
                updateAge: 60 // Refresh when 60 seconds remain before expiry
            }
        }
    }
});

无状态会话的版本控制

【Versioning Stateless Sessions】

无状态会话的最大缺点之一是你无法轻松地使会话失效。为了用更好的认证来解决这个问题,如果你想使所有会话失效,可以更改 cookie 缓存的版本并重新部署你的应用。

【One of the biggest drawbacks of stateless sessions is that you can't invalidate session easily. To solve this with better auth, if you would like to invalidate all sessions, you can change the version of the cookie cache and re-deploy your application.】

auth.ts
export const auth = betterAuth({
    session: {
        cookieCache: {
            version: "2", // Change the version to invalidate all sessions
        }
    }
});

这将使所有与新版本不匹配的会话失效。

无状态与辅助存储

【Stateless with Secondary Storage】

你可以将无状态会话与二级存储(如 Redis)结合使用,以兼得两者的优势:

【You can combine stateless sessions with secondary storage (Redis, etc.) for the best of both worlds:】

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

export const auth = betterAuth({
    // No primary database needed
    secondaryStorage: {
        get: async (key) => await redis.get(key),
        set: async (key, value, ttl) => await redis.set(key, value, "EX", ttl),
        delete: async (key) => await redis.del(key)
    },
    session: {
        cookieCache: {
            maxAge: 5 * 60, // 5 minutes (short-lived cookie)
            refreshCache: false // Disable stateless refresh
        }
    }
});

该设置:

  • 使用 Cookie 进行会话验证(无需数据库查询)
  • 使用 Redis 存储会话数据,并在 Cookie 过期前刷新缓存
  • 你可以从二级存储中撤销会话,刷新时 Cookie 缓存将被作废

【This setup:

  • Uses cookies for session validation (no DB queries)
  • Uses Redis for storing session data and refreshing the cookie cache before expiry
  • You can revoke sessions from the secondary storage and the cookie cache will be invalidated on refresh】

自定义会话响应

【Customizing Session Response】

当你调用 getSessionuseSession 时,会返回一个包含 usersession 对象的会话数据。你可以使用 customSession 插件来定制这个响应。

【When you call getSession or useSession, the session data is returned as a user and session object. You can customize this response using the customSession plugin.】

auth.ts
import { customSession } from "better-auth/plugins";

export const auth = betterAuth({
    plugins: [
        customSession(async ({ user, session }) => {
            const roles = findUserRoles(session.session.userId);
            return {
                roles,
                user: {
                    ...user,
                    newField: "newField",
                },
                session
            };
        }),
    ],
});

这将把 rolesuser.newField 添加到会话响应中。

【This will add roles and user.newField to the session response.】

对客户进行推断

auth-client.ts
import { customSessionClient } from "better-auth/client/plugins";
import type { auth } from "@/lib/auth"; // Import the auth instance as a type

const authClient = createAuthClient({
    plugins: [customSessionClient<typeof auth>()],
});

const { data } = authClient.useSession();
const { data: sessionData } = await authClient.getSession();
// data.roles
// data.user.newField

定制会话响应的注意事项

【Caveats on Customizing Session Response】

  1. 传递给回调的 session 对象无法推断插件添加的字段。

但是,作为一种权宜之计,你可以调出你的身份验证选项并将其传递给插件来推断字段。

【However, as a workaround, you can pull up your auth options and pass it to the plugin to infer the fields.】

import { betterAuth, BetterAuthOptions } from "better-auth";

const options = {
  //...config options
  plugins: [
    //...plugins 
  ]
} satisfies BetterAuthOptions;

export const auth = betterAuth({
    ...options,
    plugins: [
        ...(options.plugins ?? []),
        customSession(async ({ user, session }, ctx) => {
            // now both user and session will infer the fields added by plugins and your custom fields
            return {
                user,
                session
            }
        }, options), // pass options here
    ]
})
  1. 当你的服务器和客户端代码位于不同的项目或代码库中,并且你无法将 auth 实例作为类型引用导入时,自定义会话字段的类型推断在客户端将无法工作。
  2. 会话缓存,包括二级存储或 Cookie 缓存,不包括自定义字段。每次获取会话时,都会调用你的自定义会话函数。

更改 list-device-sessions 接口 /multi-session/list-device-sessions 接口来自 multi-session 插件,用于列出用户已登录的设备。

你可以通过将 shouldMutateListDeviceSessionsEndpoint 选项传递给 customSession 插件来修改此端点的响应。

【You can mutate the response of this endpoint by passing the shouldMutateListDeviceSessionsEndpoint option to the customSession plugin.】

默认情况下,我们不会修改此端点的响应。

【By default, we do not mutate the response of this endpoint.】

auth.ts
import { customSession } from "better-auth/plugins";

export const auth = betterAuth({
    plugins: [
        customSession(async ({ user, session }, ctx) => {
            return {
                user,
                session
            }
        }, {}, { shouldMutateListDeviceSessionsEndpoint: true }), 
    ],
});