Loading...
Loading...
Compare original and translation side by side
bknd-setup-authbknd-login-flowbknd@bknd/reactbknd-setup-authbknd-login-flowbknd@bknd/reactimport { defineConfig } from "bknd";
export default defineConfig({
auth: {
enabled: true,
jwt: {
secret: process.env.JWT_SECRET!, // Required for production
alg: "HS256", // Algorithm: HS256 | HS384 | HS512
expires: 604800, // 7 days in seconds
issuer: "my-app", // Token issuer claim
fields: ["id", "email", "role"], // User fields in token payload
},
},
});| Option | Type | Default | Description |
|---|---|---|---|
| string | | Signing secret (256-bit min for production) |
| string | | HMAC algorithm |
| number | - | Token lifetime in seconds |
| string | - | Issuer claim (iss) |
| string[] | | User fields encoded in token |
import { defineConfig } from "bknd";
export default defineConfig({
auth: {
enabled: true,
jwt: {
secret: process.env.JWT_SECRET!, // 生产环境必填
alg: "HS256", // 算法:HS256 | HS384 | HS512
expires: 604800, // 有效期7天,单位秒
issuer: "my-app", // 令牌发行方声明
fields: ["id", "email", "role"], // 令牌载荷中的用户字段
},
},
});| 选项 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| string | | 签名密钥(生产环境至少256位) |
| string | | HMAC算法 |
| number | - | 令牌有效期(秒) |
| string | - | 发行方声明(iss) |
| string[] | | 编码到令牌中的用户字段 |
{
auth: {
cookie: {
secure: process.env.NODE_ENV === "production", // HTTPS only
httpOnly: true, // No JS access
sameSite: "lax", // CSRF protection
expires: 604800, // Match JWT expiry
renew: true, // Auto-extend on activity
path: "/", // Cookie scope
pathSuccess: "/dashboard", // Redirect after login
pathLoggedOut: "/login", // Redirect after logout
},
},
}| Option | Type | Default | Description |
|---|---|---|---|
| boolean | | Require HTTPS |
| boolean | | Block JavaScript access |
| string | | |
| number | | Cookie lifetime (seconds) |
| boolean | | Auto-renew on requests |
| string | | Post-login redirect |
| string | | Post-logout redirect |
{
auth: {
cookie: {
secure: process.env.NODE_ENV === "production", // 仅HTTPS环境启用
httpOnly: true, // 禁止JavaScript访问
sameSite: "lax", // CSRF保护
expires: 604800, // 与JWT有效期匹配
renew: true, // 有活动时自动续期
path: "/", // Cookie作用域
pathSuccess: "/dashboard", // 登录后重定向路径
pathLoggedOut: "/login", // 登出后重定向路径
},
},
}| 选项 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| boolean | | 要求HTTPS环境 |
| boolean | | 阻止JavaScript访问 |
| string | | 可选值: |
| number | | Cookie有效期(秒) |
| boolean | | 请求时自动续期 |
| string | | 登录后重定向路径 |
| string | | 登出后重定向路径 |
import { Api } from "bknd";
// Persistent sessions (survives page refresh/browser restart)
const api = new Api({
host: "http://localhost:7654",
storage: localStorage, // Token persisted
});
// Session-only (cleared when tab closes)
const api = new Api({
host: "http://localhost:7654",
storage: sessionStorage, // Token cleared on tab close
});
// No persistence (token in memory only)
const api = new Api({
host: "http://localhost:7654",
// No storage = token lost on page refresh
});import { Api } from "bknd";
// 持久化会话(页面刷新/浏览器重启后仍保留)
const api = new Api({
host: "http://localhost:7654",
storage: localStorage, // 令牌持久化存储
});
// 仅会话级存储(标签页关闭后清除)
const api = new Api({
host: "http://localhost:7654",
storage: sessionStorage, // 标签页关闭后令牌清除
});
// 无持久化(仅内存中存储令牌)
const api = new Api({
host: "http://localhost:7654",
// 不设置storage,页面刷新后令牌丢失
});async function initializeAuth() {
const api = new Api({
host: "http://localhost:7654",
storage: localStorage,
});
// Check if existing token is still valid
const { ok, data } = await api.auth.me();
if (ok && data?.user) {
console.log("Session valid:", data.user.email);
return { api, user: data.user };
}
console.log("No valid session");
return { api, user: null };
}
// On app mount
const { api, user } = await initializeAuth();async function initializeAuth() {
const api = new Api({
host: "http://localhost:7654",
storage: localStorage,
});
// 检查现有令牌是否仍有效
const { ok, data } = await api.auth.me();
if (ok && data?.user) {
console.log("会话有效:", data.user.email);
return { api, user: data.user };
}
console.log("无有效会话");
return { api, user: null };
}
// 应用挂载时调用
const { api, user } = await initializeAuth();import { Api } from "bknd";
class SessionManager {
private api: Api;
private user: User | null = null;
private listeners: Set<(user: User | null) => void> = new Set();
constructor(host: string) {
this.api = new Api({ host, storage: localStorage });
}
// Initialize - call on app start
async init() {
const { ok, data } = await this.api.auth.me();
this.user = ok ? data?.user ?? null : null;
this.notifyListeners();
return this.user;
}
// Get current session
getUser() {
return this.user;
}
isAuthenticated() {
return this.user !== null;
}
// Login - creates new session
async login(email: string, password: string) {
const { ok, data, error } = await this.api.auth.login("password", {
email,
password,
});
if (!ok) throw new Error(error?.message || "Login failed");
this.user = data!.user;
this.notifyListeners();
return this.user;
}
// Logout - destroys session
async logout() {
await this.api.auth.logout();
this.user = null;
this.notifyListeners();
}
// Refresh session (re-validate token)
async refresh() {
const { ok, data } = await this.api.auth.me();
this.user = ok ? data?.user ?? null : null;
this.notifyListeners();
return this.user;
}
// Subscribe to session changes
subscribe(callback: (user: User | null) => void) {
this.listeners.add(callback);
return () => this.listeners.delete(callback);
}
private notifyListeners() {
this.listeners.forEach((cb) => cb(this.user));
}
}
type User = { id: number; email: string; role?: string };
// Usage
const session = new SessionManager("http://localhost:7654");
await session.init();
session.subscribe((user) => {
console.log("Session changed:", user?.email || "logged out");
});import { Api } from "bknd";
class SessionManager {
private api: Api;
private user: User | null = null;
private listeners: Set<(user: User | null) => void> = new Set();
constructor(host: string) {
this.api = new Api({ host, storage: localStorage });
}
// 初始化 - 应用启动时调用
async init() {
const { ok, data } = await this.api.auth.me();
this.user = ok ? data?.user ?? null : null;
this.notifyListeners();
return this.user;
}
// 获取当前会话
getUser() {
return this.user;
}
isAuthenticated() {
return this.user !== null;
}
// 登录 - 创建新会话
async login(email: string, password: string) {
const { ok, data, error } = await this.api.auth.login("password", {
email,
password,
});
if (!ok) throw new Error(error?.message || "登录失败");
this.user = data!.user;
this.notifyListeners();
return this.user;
}
// 登出 - 销毁会话
async logout() {
await this.api.auth.logout();
this.user = null;
this.notifyListeners();
}
// 刷新会话(重新验证令牌)
async refresh() {
const { ok, data } = await this.api.auth.me();
this.user = ok ? data?.user ?? null : null;
this.notifyListeners();
return this.user;
}
// 订阅会话变更
subscribe(callback: (user: User | null) => void) {
this.listeners.add(callback);
return () => this.listeners.delete(callback);
}
private notifyListeners() {
this.listeners.forEach((cb) => cb(this.user));
}
}
type User = { id: number; email: string; role?: string };
// 使用示例
const session = new SessionManager("http://localhost:7654");
await session.init();
session.subscribe((user) => {
console.log("会话变更:", user?.email || "已登出");
});const api = new Api({
host: "http://localhost:7654",
tokenTransport: "cookie", // Use httpOnly cookies
});
// Login sets cookie automatically
await api.auth.login("password", { email, password });
// All requests include cookie automatically
await api.data.readMany("posts");
// Logout clears cookie
await api.auth.logout();cookie.renew: truesameSiteconst api = new Api({
host: "http://localhost:7654",
tokenTransport: "cookie", // 使用HttpOnly Cookie
});
// 登录时自动设置Cookie
await api.auth.login("password", { email, password });
// 所有请求自动携带Cookie
await api.data.readMany("posts");
// 登出时自动清除Cookie
await api.auth.logout();cookie.renew: truesameSiteconst api = new Api({
host: "http://localhost:7654",
storage: localStorage,
tokenTransport: "header", // Default
});
// Token stored in localStorage, sent via Authorization header
await api.auth.login("password", { email, password });
// Token automatically included:
// Authorization: Bearer eyJhbGciOiJIUzI1NiIs...const api = new Api({
host: "http://localhost:7654",
storage: localStorage,
tokenTransport: "header", // 默认值
});
// 令牌存储在localStorage中,通过Authorization头发送
await api.auth.login("password", { email, password });
// 令牌自动包含在请求中:
// Authorization: Bearer eyJhbGciOiJIUzI1NiIs...async function makeAuthenticatedRequest<T>(fn: () => Promise<T>): Promise<T> {
try {
return await fn();
} catch (error) {
// Check if error is due to expired session
if (isAuthError(error)) {
// Session expired - redirect to login or refresh
await handleExpiredSession();
}
throw error;
}
}
function isAuthError(error: unknown): boolean {
if (error instanceof Error) {
return error.message.includes("401") || error.message.includes("Unauthorized");
}
return false;
}
async function handleExpiredSession() {
// Option 1: Redirect to login
window.location.href = "/login?expired=true";
// Option 2: Show re-authentication modal
// showReauthModal();
// Option 3: Try to refresh (if using refresh tokens)
// await refreshToken();
}async function makeAuthenticatedRequest<T>(fn: () => Promise<T>): Promise<T> {
try {
return await fn();
} catch (error) {
// 检查错误是否由会话过期导致
if (isAuthError(error)) {
// 会话过期 - 重定向到登录页或刷新会话
await handleExpiredSession();
}
throw error;
}
}
function isAuthError(error: unknown): boolean {
if (error instanceof Error) {
return error.message.includes("401") || error.message.includes("Unauthorized");
}
return false;
}
async function handleExpiredSession() {
// 选项1:重定向到登录页
window.location.href = "/login?expired=true";
// 选项2:显示重新认证弹窗
// showReauthModal();
// 选项3:尝试刷新令牌(如果使用刷新令牌)
// await refreshToken();
}api.auth.me()class SessionWithAutoRefresh {
private api: Api;
private refreshInterval: number | null = null;
constructor(host: string) {
this.api = new Api({
host,
tokenTransport: "cookie", // Cookie auto-renews on requests
});
}
// Start periodic session check
startAutoRefresh(intervalMs = 5 * 60 * 1000) {
// Every 5 minutes
this.refreshInterval = window.setInterval(async () => {
const { ok } = await this.api.auth.me();
if (!ok) {
this.stopAutoRefresh();
this.onSessionExpired();
}
}, intervalMs);
}
stopAutoRefresh() {
if (this.refreshInterval) {
clearInterval(this.refreshInterval);
this.refreshInterval = null;
}
}
private onSessionExpired() {
// Handle expired session
window.location.href = "/login?session=expired";
}
}api.auth.me()class SessionWithAutoRefresh {
private api: Api;
private refreshInterval: number | null = null;
constructor(host: string) {
this.api = new Api({
host,
tokenTransport: "cookie", // Cookie会在请求时自动续期
});
}
// 启动定期会话检查
startAutoRefresh(intervalMs = 5 * 60 * 1000) {
// 每5分钟检查一次
this.refreshInterval = window.setInterval(async () => {
const { ok } = await this.api.auth.me();
if (!ok) {
this.stopAutoRefresh();
this.onSessionExpired();
}
}, intervalMs);
}
stopAutoRefresh() {
if (this.refreshInterval) {
clearInterval(this.refreshInterval);
this.refreshInterval = null;
}
}
private onSessionExpired() {
// 处理会话过期
window.location.href = "/login?session=expired";
}
}import { jwtDecode } from "jwt-decode"; // npm install jwt-decode
class TokenManager {
private api: Api;
private refreshTimer: number | null = null;
constructor(host: string) {
this.api = new Api({ host, storage: localStorage });
}
// Schedule refresh before expiry
scheduleRefresh(token: string) {
const decoded = jwtDecode<{ exp: number }>(token);
const expiresAt = decoded.exp * 1000; // Convert to ms
const refreshAt = expiresAt - 5 * 60 * 1000; // 5 min before expiry
const delay = refreshAt - Date.now();
if (delay > 0) {
this.refreshTimer = window.setTimeout(() => {
this.promptRelogin();
}, delay);
}
}
private promptRelogin() {
// Show modal asking user to re-authenticate
// Or redirect to login with return URL
}
cleanup() {
if (this.refreshTimer) {
clearTimeout(this.refreshTimer);
}
}
}import { jwtDecode } from "jwt-decode"; // npm install jwt-decode
class TokenManager {
private api: Api;
private refreshTimer: number | null = null;
constructor(host: string) {
this.api = new Api({ host, storage: localStorage });
}
// 在过期前安排刷新
scheduleRefresh(token: string) {
const decoded = jwtDecode<{ exp: number }>(token);
const expiresAt = decoded.exp * 1000; // 转换为毫秒
const refreshAt = expiresAt - 5 * 60 * 1000; // 过期前5分钟刷新
const delay = refreshAt - Date.now();
if (delay > 0) {
this.refreshTimer = window.setTimeout(() => {
this.promptRelogin();
}, delay);
}
}
private promptRelogin() {
// 显示弹窗要求用户重新认证
// 或重定向到登录页并携带返回URL
}
cleanup() {
if (this.refreshTimer) {
clearTimeout(this.refreshTimer);
}
}
}import { createContext, useContext, useEffect, useState, ReactNode } from "react";
import { Api } from "bknd";
type User = { id: number; email: string; role?: string };
type SessionContextType = {
user: User | null;
isLoading: boolean;
isAuthenticated: boolean;
checkSession: () => Promise<User | null>;
clearSession: () => void;
};
const SessionContext = createContext<SessionContextType | null>(null);
const api = new Api({
host: "http://localhost:7654",
storage: localStorage,
});
export function SessionProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [isLoading, setIsLoading] = useState(true);
// Check session on mount
useEffect(() => {
checkSession().finally(() => setIsLoading(false));
}, []);
async function checkSession() {
const { ok, data } = await api.auth.me();
const user = ok ? data?.user ?? null : null;
setUser(user);
return user;
}
function clearSession() {
setUser(null);
api.auth.logout();
}
return (
<SessionContext.Provider
value={{
user,
isLoading,
isAuthenticated: user !== null,
checkSession,
clearSession,
}}
>
{children}
</SessionContext.Provider>
);
}
export function useSession() {
const context = useContext(SessionContext);
if (!context) throw new Error("useSession must be used within SessionProvider");
return context;
}import { createContext, useContext, useEffect, useState, ReactNode } from "react";
import { Api } from "bknd";
type User = { id: number; email: string; role?: string };
type SessionContextType = {
user: User | null;
isLoading: boolean;
isAuthenticated: boolean;
checkSession: () => Promise<User | null>;
clearSession: () => void;
};
const SessionContext = createContext<SessionContextType | null>(null);
const api = new Api({
host: "http://localhost:7654",
storage: localStorage,
});
export function SessionProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [isLoading, setIsLoading] = useState(true);
// 挂载时检查会话
useEffect(() => {
checkSession().finally(() => setIsLoading(false));
}, []);
async function checkSession() {
const { ok, data } = await api.auth.me();
const user = ok ? data?.user ?? null : null;
setUser(user);
return user;
}
function clearSession() {
setUser(null);
api.auth.logout();
}
return (
<SessionContext.Provider
value={{
user,
isLoading,
isAuthenticated: user !== null,
checkSession,
clearSession,
}}
>
{children}
</SessionContext.Provider>
);
}
export function useSession() {
const context = useContext(SessionContext);
if (!context) throw new Error("useSession必须在SessionProvider内部使用");
return context;
}import { useSession } from "./SessionProvider";
function Header() {
const { user, isAuthenticated, clearSession } = useSession();
if (!isAuthenticated) {
return <a href="/login">Login</a>;
}
return (
<div>
<span>Welcome, {user!.email}</span>
<button onClick={clearSession}>Logout</button>
</div>
);
}
function ProtectedPage() {
const { isLoading, isAuthenticated } = useSession();
if (isLoading) return <div>Checking session...</div>;
if (!isAuthenticated) return <Navigate to="/login" />;
return <div>Protected content</div>;
}import { useSession } from "./SessionProvider";
function Header() {
const { user, isAuthenticated, clearSession } = useSession();
if (!isAuthenticated) {
return <a href="/login">登录</a>;
}
return (
<div>
<span>欢迎,{user!.email}</span>
<button onClick={clearSession}>登出</button>
</div>
);
}
function ProtectedPage() {
const { isLoading, isAuthenticated } = useSession();
if (isLoading) return <div>检查会话中...</div>;
if (!isAuthenticated) return <Navigate to="/login" />;
return <div>受保护内容</div>;
}import { useEffect } from "react";
import { useSession } from "./SessionProvider";
function SessionExpirationHandler() {
const { checkSession, clearSession } = useSession();
useEffect(() => {
// Check session periodically
const interval = setInterval(async () => {
const user = await checkSession();
if (!user) {
// Session expired
alert("Your session has expired. Please log in again.");
clearSession();
window.location.href = "/login";
}
}, 5 * 60 * 1000); // Every 5 minutes
// Check on window focus (user returns to tab)
const handleFocus = () => checkSession();
window.addEventListener("focus", handleFocus);
return () => {
clearInterval(interval);
window.removeEventListener("focus", handleFocus);
};
}, [checkSession, clearSession]);
return null; // Invisible component
}
// Add to app root
function App() {
return (
<SessionProvider>
<SessionExpirationHandler />
<Routes />
</SessionProvider>
);
}import { useEffect } from "react";
import { useSession } from "./SessionProvider";
function SessionExpirationHandler() {
const { checkSession, clearSession } = useSession();
useEffect(() => {
// 定期检查会话
const interval = setInterval(async () => {
const user = await checkSession();
if (!user) {
// 会话已过期
alert("您的会话已过期,请重新登录。");
clearSession();
window.location.href = "/login";
}
}, 5 * 60 * 1000); // 每5分钟检查一次
// 窗口获得焦点时检查(用户返回标签页)
const handleFocus = () => checkSession();
window.addEventListener("focus", handleFocus);
return () => {
clearInterval(interval);
window.removeEventListener("focus", handleFocus);
};
}, [checkSession, clearSession]);
return null; // 不可见组件
}
// 添加到应用根节点
function App() {
return (
<SessionProvider>
<SessionExpirationHandler />
<Routes />
</SessionProvider>
);
}import { getApi } from "bknd";
export async function GET(request: Request, app: BkndApp) {
const api = getApi(app);
const user = await api.auth.resolveAuthFromRequest(request);
if (!user) {
return new Response("Unauthorized", { status: 401 });
}
// Session valid - user data available
console.log("User ID:", user.id);
console.log("Email:", user.email);
console.log("Role:", user.role);
return new Response(JSON.stringify({ user }));
}import { getApi } from "bknd";
export async function GET(request: Request, app: BkndApp) {
const api = getApi(app);
const user = await api.auth.resolveAuthFromRequest(request);
if (!user) {
return new Response("未授权", { status: 401 });
}
// 会话有效 - 用户数据可用
console.log("用户ID:", user.id);
console.log("邮箱:", user.email);
console.log("角色:", user.role);
return new Response(JSON.stringify({ user }));
}// app/api/me/route.ts
import { getApp, getApi } from "bknd/adapter/nextjs";
export async function GET(request: Request) {
const app = await getApp();
const api = getApi(app);
const user = await api.auth.resolveAuthFromRequest(request);
if (!user) {
return Response.json({ user: null }, { status: 401 });
}
return Response.json({ user });
}// app/api/me/route.ts
import { getApp, getApi } from "bknd/adapter/nextjs";
export async function GET(request: Request) {
const app = await getApp();
const api = getApi(app);
const user = await api.auth.resolveAuthFromRequest(request);
if (!user) {
return Response.json({ user: null }, { status: 401 });
}
return Response.json({ user });
}// Track user activity for session timeout warnings
let lastActivity = Date.now();
// Update on user interaction
document.addEventListener("click", () => (lastActivity = Date.now()));
document.addEventListener("keypress", () => (lastActivity = Date.now()));
// Check for inactivity
setInterval(() => {
const inactiveMinutes = (Date.now() - lastActivity) / 1000 / 60;
if (inactiveMinutes > 25) {
// Warn user session will expire soon
showSessionWarning();
}
if (inactiveMinutes > 30) {
// Force logout
api.auth.logout();
window.location.href = "/login?reason=inactive";
}
}, 60000); // Check every minute// 跟踪用户活动以发出会话超时警告
let lastActivity = Date.now();
// 用户交互时更新
document.addEventListener("click", () => (lastActivity = Date.now()));
document.addEventListener("keypress", () => (lastActivity = Date.now()));
// 检查是否处于非活动状态
setInterval(() => {
const inactiveMinutes = (Date.now() - lastActivity) / 1000 / 60;
if (inactiveMinutes > 25) {
// 警告用户会话即将过期
showSessionWarning();
}
if (inactiveMinutes > 30) {
// 强制登出
api.auth.logout();
window.location.href = "/login?reason=inactive";
}
}, 60000); // 每分钟检查一次// Sync session state across browser tabs
window.addEventListener("storage", async (event) => {
if (event.key === "auth") {
if (event.newValue === null) {
// Logged out in another tab
window.location.href = "/login";
} else {
// Logged in in another tab - refresh session
await api.auth.me();
window.location.reload();
}
}
});// 同步浏览器标签页间的会话状态
window.addEventListener("storage", async (event) => {
if (event.key === "auth") {
if (event.newValue === null) {
// 在其他标签页已登出
window.location.href = "/login";
} else {
// 在其他标签页已登录 - 刷新会话
await api.auth.me();
window.location.reload();
}
}
});// For sensitive apps, use sessionStorage + warn on tab close
const api = new Api({
host: "http://localhost:7654",
storage: sessionStorage,
});
window.addEventListener("beforeunload", (e) => {
if (api.auth.me()) {
e.preventDefault();
e.returnValue = "You will be logged out if you leave.";
}
});// 对于敏感应用,使用sessionStorage并在标签页关闭时发出警告
const api = new Api({
host: "http://localhost:7654",
storage: sessionStorage,
});
window.addEventListener("beforeunload", (e) => {
if (api.auth.me()) {
e.preventDefault();
e.returnValue = "离开此页面将导致您登出。";
}
});// Wrong - no persistence
const api = new Api({ host: "http://localhost:7654" });
// Correct
const api = new Api({
host: "http://localhost:7654",
storage: localStorage,
});// 错误示例 - 无持久化
const api = new Api({ host: "http://localhost:7654" });
// 正确示例
const api = new Api({
host: "http://localhost:7654",
storage: localStorage,
});{
auth: {
cookie: {
secure: process.env.NODE_ENV === "production", // false in dev
},
},
}{
auth: {
cookie: {
secure: process.env.NODE_ENV === "production", // 开发环境为false
},
},
}function App() {
const { isLoading } = useSession();
if (isLoading) {
return <LoadingSpinner />; // Don't leave blank
}
return <Routes />;
}function App() {
const { isLoading } = useSession();
if (isLoading) {
return <LoadingSpinner />; // 不要留空白
}
return <Routes />;
}async function checkSession() {
const { ok } = await api.auth.me();
if (!ok) {
// Clear stale token
localStorage.removeItem("auth");
return null;
}
return user;
}async function checkSession() {
const { ok } = await api.auth.me();
if (!ok) {
// 清除过期令牌
localStorage.removeItem("auth");
return null;
}
return user;
}// Login
await api.auth.login("password", { email: "test@example.com", password: "pass" });
// Refresh page, then:
const { ok, data } = await api.auth.me();
console.log("Session persists:", ok && data?.user); // Should be true// Set short expiry in config (for testing)
jwt: { expires: 10 } // 10 seconds
// Login, wait 15 seconds
await api.auth.login("password", { email, password });
await new Promise(r => setTimeout(r, 15000));
const { ok } = await api.auth.me();
console.log("Session expired:", !ok); // Should be trueawait api.auth.logout();
const { ok } = await api.auth.me();
console.log("Session cleared:", !ok); // Should be true// 登录
await api.auth.login("password", { email: "test@example.com", password: "pass" });
// 刷新页面后执行:
const { ok, data } = await api.auth.me();
console.log("会话保持有效:", ok && data?.user); // 应为true// 配置中设置短有效期(用于测试)
jwt: { expires: 10 } // 10秒
// 登录,等待15秒
await api.auth.login("password", { email, password });
await new Promise(r => setTimeout(r, 15000));
const { ok } = await api.auth.me();
console.log("会话已过期:", !ok); // 应为trueawait api.auth.logout();
const { ok } = await api.auth.me();
console.log("会话已清除:", !ok); // 应为truesecure: truesecure: true