Token导航 LogoToken导航TokenDH.com
开发规范敏感数据github未标认证来源可访问许可证需确认审计通过

better-auth-best-practices更好的身份验证最佳实践

Agent Skill

用于辅助安全审计、权限检查、凭据风险、认证流程和常见漏洞排查。它适合让 Agent 梳理敏感配置、检查依赖风险、分析鉴权逻辑或生成安全复核清单。使用时不能把工具输出直接当最终结论,涉及密钥、令牌、用户数据或生产系统时,应先确认最小权限、脱敏方式和操作边界。

总安装

679

周安装

28

GitHub Stars

4

下载量

222
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:better-auth-best-practices(更好的身份验证最佳实践)
来源仓库:https://github.com/autumnsgrove/groveengine
仓库路径:skills/better-auth-best-practices
安装命令:
npx skills add https://github.com/autumnsgrove/groveengine --skill better-auth-best-practices
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/autumnsgrove/groveengine --skill better-auth-best-practices

简介

better-auth-best-practices 用于辅助安全审计、权限检查和认证流程漏洞排查,提供 Better Auth 框架的最佳实践参考。

  • 涵盖配置加固、速率限制、会话管理、插件集成和生产部署模式,强化身份验证安全性。
  • 可协助设置二次验证、邮箱验证、OAuth 集成和 Cookie 安全策略,降低账户风险。
  • 使用时不能将工具输出直接当作最终结论,需结合人工复核敏感配置和操作边界。
  • 涉及用户数据或生产系统时,应先确认最小权限原则和脱敏处理,避免越权访问。

SKILL.md

Better Auth Best Practices

Comprehensive reference for the Better Auth framework. Covers configuration, security hardening, rate limiting, session management, plugins, and production deployment patterns.

Canonical docs: better-auth.com/docs Source: Synthesized from better-auth/skills (official upstream) + Grove production experience.

When to Activate

  • Configuring or modifying Better Auth server/client setup
  • Auditing auth security (pair with raccoon-audit, turtle-harden)
  • Adding or configuring rate limiting
  • Setting up session management, cookie caching, or secondary storage
  • Adding plugins (2FA, organizations, passkeys, etc.)
  • Troubleshooting auth issues on Heartwood or any Better Auth deployment
  • Reviewing security posture before production deploy

Pair with: heartwood-auth (Grove-specific integration), spider-weave (auth architecture), turtle-harden (deep security)


Grove Context: Heartwood

Heartwood is Grove's auth service, powered by Better Auth on Cloudflare Workers.

ComponentDetail
Frontendheartwood.grove.place
APIauth-api.grove.place
DatabaseCloudflare D1 (SQLite)
Session cacheCloudflare KV (SESSION_KV)
ProvidersGoogle OAuth, Magic Links, Passkeys
Cookie domain.grove.place (cross-subdomain SSO)

Everything in this skill applies directly to Heartwood. The heartwood-auth skill covers Grove-specific integration patterns (client setup, route protection, error codes). This skill covers the framework itself.


Quick Reference

Environment Variables

VariablePurpose
BETTER_AUTH_SECRETEncryption secret (min 32 chars). Generate: openssl rand -base64 32
BETTER_AUTH_URLBase URL (e.g., https://auth-api.grove.place)
BETTER_AUTH_TRUSTED_ORIGINSComma-separated trusted origins

Only define baseURL/secret in config if env vars are NOT set.

File Location

CLI looks for auth.ts in: ./, ./lib, ./utils, or under ./src. Use --config for custom path.

CLI Commands

npx @better-auth/cli@latest migrate    # Apply schema (built-in adapter)
npx @better-auth/cli@latest generate   # Generate schema for Prisma/Drizzle

Re-run after adding/changing plugins.


Core Configuration

OptionNotes
appNameDisplay name (used in 2FA issuer, emails)
baseURLOnly if BETTER_AUTH_URL not set
basePathDefault /api/auth. Set / for root
secretOnly if BETTER_AUTH_SECRET not set
databaseRequired. Connection or adapter instance
secondaryStorageRedis/KV for sessions & rate limits
emailAndPassword{enabled: true} to activate
socialProviders{google: {clientId, clientSecret},...}
pluginsArray of plugins
trustedOriginsCSRF whitelist (baseURL auto-trusted)

Database

Direct connections: Pass pg.Pool, mysql2 pool, better-sqlite3, or bun:sqlite instance.

ORM adapters: Import from better-auth/adapters/drizzle, better-auth/adapters/prisma, better-auth/adapters/mongodb.

Critical gotcha: Better Auth uses adapter model names, NOT underlying table names. If Prisma model is User mapping to table users, use modelName: "user" (Prisma reference), not "users".


Rate Limiting

Better Auth has built-in rate limiting — enabled by default in production, disabled in development.

Why This Matters for Grove

Better Auth's rate limiter can replace custom threshold SDKs for auth endpoints. It's battle-tested, configurable per-endpoint, and integrates directly with the auth layer where it matters most.

Default Configuration

import { betterAuth } from "better-auth";

export const auth = betterAuth({
  rateLimit: {
    enabled: true,       // Default: true in production
    window: 10,          // Time window in seconds (default: 10)
    max: 100,            // Max requests per window (default: 100)
  },
});

Storage Options

rateLimit: {
  storage: "secondary-storage", // Best for production
}
StorageBehavior
"memory"Fast, resets on restart. Not recommended for serverless.
"database"Persistent, adds DB load
"secondary-storage"Uses configured KV/Redis. Default when available.

For Heartwood: Use "secondary-storage" backed by Cloudflare KV.

Per-Endpoint Rules

Better Auth applies stricter defaults to sensitive endpoints:

  • /sign-in, /sign-up, /change-password, /change-email: 3 requests per 10 seconds

Override for specific paths:

rateLimit: {
  customRules: {
    "/api/auth/sign-in/email": {
      window: 60,  // 1 minute
      max: 5,      // 5 attempts
    },
    "/api/auth/sign-up/email": {
      window: 60,
      max: 3,      // Very strict for registration
    },
    "/api/auth/some-safe-endpoint": false, // Disable rate limiting
  },
}

Custom Storage

For non-standard backends:

rateLimit: {
  customStorage: {
    get: async (key) => {
      // Return { count: number, expiresAt: number } or null
    },
    set: async (key, data) => {
      // Store the rate limit data
    },
  },
}

Each plugin can optionally define its own rate-limit rules per endpoint.


Session Management

Storage Priority

  1. If secondaryStorage defined → sessions go there (not DB)
  2. Set session.storeSessionInDatabase: true to also persist to DB
  3. No database + cookieCache → fully stateless mode

Key Options

session: {
  expiresIn: 60 * 60 * 24 * 7,   // 7 days (default)
  updateAge: 60 * 60 * 24,        // Refresh every 24 hours (default)
  freshAge: 60 * 60 * 24,         // 24 hours for sensitive actions (default)
}

freshAge — defines how recently a user must have authenticated to perform sensitive operations. Use to require re-auth for password changes, viewing sensitive data, etc.

Cookie Cache Strategies

Cache session data in cookies to reduce DB/KV queries:

session: {
  cookieCache: {
    enabled: true,
    maxAge: 60 * 5,        // 5 minutes
    strategy: "compact",   // Options: "compact", "jwt", "jwe"
    version: 1,            // Change to invalidate all sessions
  },
}
StrategyDescription
compactBase64url + HMAC. Smallest size. Default.
jwtStandard HS256 JWT. Readable but signed.
jweA256CBC-HS512 encrypted. Maximum security.

Gotcha: Custom session fields are NOT cached — they're always re-fetched from storage.


Security Configuration

Secret Management

Better Auth looks for secrets in order:

  1. options.secret in config
  2. BETTER_AUTH_SECRET env var
  3. AUTH_SECRET env var

Requirements:

  • Rejects default/placeholder secrets in production
  • Warns if shorter than 32 characters
  • Warns if entropy below 120 bits

CSRF Protection

Multi-layered by default:

  1. Origin header validationOrigin/Referer must match trusted origins
  2. Fetch metadata — Uses Sec-Fetch-Site, Sec-Fetch-Mode, Sec-Fetch-Dest headers
  3. First-login protection — Validates origin even without cookies
advanced: {
  disableCSRFCheck: false,  // KEEP THIS FALSE
}

Trusted Origins

trustedOrigins: [
  "https://app.grove.place",
  "https://*.grove.place",          // Wildcard subdomain
  "exp://192.168.*.*:*/*",          // Custom schemes (Expo)
]

Dynamic computation:

trustedOrigins: async (request) => {
  const tenant = getTenantFromRequest(request);
  return [`https://${tenant}.grove.place`];
}

Validated parameters: callbackURL, redirectTo, errorCallbackURL, newUserCallbackURL, origin, and more. Invalid URLs get 403.

Cookie Security

Defaults are secure:

  • secure: true when baseURL uses HTTPS or in production
  • sameSite: "lax" (CSRF prevention while allowing navigation)
  • httpOnly: true (no JavaScript access)
  • __Secure- prefix when secure is enabled
advanced: {
  useSecureCookies: true,
  cookiePrefix: "better-auth",
  defaultCookieAttributes: {
    sameSite: "lax",
  },
  crossSubDomainCookies: {
    enabled: true,
    domain: ".grove.place",  // Note the leading dot
    additionalCookies: ["session_token", "session_data"],
  },
}

Warning: Cross-subdomain cookies expand attack surface. Only enable if you trust all subdomains.

IP-Based Security

advanced: {
  ipAddress: {
    ipAddressHeaders: ["x-forwarded-for", "x-real-ip"],
    ipv6Subnet: 64,            // Group IPv6 by /64
    disableIpTracking: false,  // Keep enabled for rate limiting
  },
  trustedProxyHeaders: true,   // Only if behind a trusted proxy
}

Background Tasks (Timing Attack Prevention)

Sensitive operations should complete in constant time. The handler callback receives a promise that must outlive the response — on serverless platforms, you need the platform's waitUntil to keep it alive.

Cloudflare Workers: Capture ExecutionContext from the fetch handler and close over it:

// In your Worker fetch handler:
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    // Create auth with ctx in scope
    const auth = createAuth(env, ctx);
    return auth.handler(request);
  },
};

// In your auth config factory:
function createAuth(env: Env, ctx: ExecutionContext) {
  return betterAuth({
    // ...
    advanced: {
      backgroundTasks: {
        handler: (promise) => ctx.waitUntil(promise),
      },
    },
  });
}

Vercel/Next.js: Use the waitUntil export from @vercel/functions:

import { waitUntil } from "@vercel/functions";

advanced: {
  backgroundTasks: {
    handler: (promise) => waitUntil(promise),
  },
}

Ensures email sending doesn't leak information about whether a user exists.

Account Enumeration Prevention

Built-in protections:

  1. Consistent response messages — Password reset always returns generic message
  2. Dummy operations — When user isn't found, still performs token generation + DB lookups
  3. Background email sending — Async to prevent timing differences

OAuth / Social Provider Security

PKCE (Automatic)

Better Auth automatically uses PKCE for all OAuth flows:

  1. Generates 128-character random code_verifier
  2. Creates code_challenge using S256 (SHA-256)
  3. Validates code exchange with original verifier

State Parameter

account: {
  storeStateStrategy: "cookie",  // "cookie" (default) or "database"
}

State tokens: 32-character random strings, expire after 10 minutes, contain encrypted callback URLs + PKCE verifier.

Encrypt Stored OAuth Tokens

account: {
  encryptOAuthTokens: true,  // AES-256-GCM
}

Enable if you store OAuth tokens for API access on behalf of users.


Email & Password

Email Verification

emailVerification: {
  sendVerificationEmail: async ({ user, url }) => {
    await sendEmail({ to: user.email, subject: "Verify your email", url });
  },
  sendOnSignUp: true,
  requireEmailVerification: true,  // Blocks sign-in until verified
}

Password Reset

emailAndPassword: {
  sendResetPassword: async ({ user, url }) => {
    await sendEmail({ to: user.email, subject: "Reset your password", url });
  },
  password: {
    minLength: 8,   // Default
    maxLength: 128,  // Default
  },
  revokeSessionsOnPasswordReset: true,  // Log out all sessions
}

Security: Reset tokens are 24-character alphanumeric strings, expire after 1 hour, single-use.

Password Hashing

Default: scrypt. For Argon2id, provide custom hash and verify functions.


Two-Factor Authentication

Setup

import { twoFactor } from "better-auth/plugins";

// Server
plugins: [
  twoFactor({
    issuer: "Grove",  // Shown in authenticator apps
    totpOptions: { digits: 6, period: 30 },
    backupCodeOptions: { amount: 10, length: 10, storeBackupCodes: "encrypted" },
  }),
]

// Client
plugins: [
  twoFactorClient({
    onTwoFactorRedirect() {
      window.location.href = "/2fa";
    },
  }),
]

Run migrations after adding. The twoFactorEnabled flag only activates after successful TOTP verification.

Sign-In Flow with 2FA

  1. User signs in with credentials
  2. Response includes twoFactorRedirect: true
  3. Session cookie removed temporarily
  4. Two-factor cookie set (10-minute expiration)
  5. User verifies via TOTP/OTP/backup code
  6. Session cookie restored

Trusted Devices

Skip 2FA on subsequent sign-ins:

twoFactor({
  trustDeviceMaxAge: 30 * 24 * 60 * 60,  // 30 days
})

// During verification:
await authClient.twoFactor.verifyTotp({ code, trustDevice: true });

OTP (Email/SMS)

twoFactor({
  otpOptions: {
    sendOTP: async ({ user, otp }) => {
      await sendEmail({ to: user.email, subject: "Your code", text: `Code: ${otp}` });
    },
    period: 5,            // Minutes
    digits: 6,
    allowedAttempts: 5,
    storeOTP: "encrypted",
  },
})

Built-in 2FA Protections

  • Rate limiting: 3 requests per 10 seconds on 2FA endpoints
  • OTP attempt limiting: configurable max attempts
  • Constant-time comparison prevents timing attacks
  • TOTP secrets encrypted with symmetric encryption
  • Backup codes encrypted by default
  • Limitation: 2FA requires credential accounts — social-only accounts can't enable it

Organizations Plugin

Multi-tenant organization support:

import { organization } from "better-auth/plugins";

plugins: [
  organization({
    // Limit who can create orgs
    allowUserToCreateOrganization: async (user) => {
      return user.emailVerified;
    },
  }),
]

Key Concepts

  • Active organization stored in session — scopes API calls after setActive()
  • Default roles: owner (full), admin (management), member (basic)
  • Dynamic access control for custom runtime permissions
  • Teams group members within organizations
  • Invitations expire after 48 hours (configurable), email-specific
  • Safety: Last owner cannot be removed or leave

Plugins Reference

import { twoFactor, organization } from "better-auth/plugins";
PluginPurposeScoped Package?
twoFactorTOTP/OTP/backup codesNo
organizationTeams & multi-tenantNo
passkeyWebAuthn@better-auth/passkey
magicLinkPasswordless emailNo
emailOtpEmail-based OTPNo
usernameUsername authNo
phoneNumberPhone authNo
adminUser managementNo
apiKeyAPI key authNo
bearerBearer token authNo
jwtJWT tokensNo
multiSessionMultiple sessionsNo
ssoSAML/OIDC enterprise@better-auth/sso
oauthProviderBe an OAuth providerNo
oidcProviderBe an OIDC providerNo
openAPIAPI documentationNo
genericOAuthCustom OAuth providerNo

Client plugins go in createAuthClient({plugins: [...]}).

Always run migrations after adding plugins.


Client Setup

Import by framework:

FrameworkImport
React/Next.jsbetter-auth/react
Svelte/SvelteKitbetter-auth/svelte
Vue/Nuxtbetter-auth/vue
Solidbetter-auth/solid
Vanilla JSbetter-auth/client

Key methods: signUp.email(), signIn.email(), signIn.social(), signOut(), useSession(), getSession(), revokeSession(), revokeSessions().

Type Safety

// Infer types from server config
type Session = typeof auth.$Infer.Session;
type User = typeof auth.$Infer.Session.user;

// For separate client/server projects
createAuthClient<typeof auth>();

Hooks

Endpoint Hooks

hooks: {
  before: [
    {
      matcher: (ctx) => ctx.path === "/sign-in/email",
      handler: createAuthMiddleware(async (ctx) => {
        // Access: ctx.path, ctx.context.session, ctx.context.secret
        // Return modified context or void
      }),
    },
  ],
  after: [
    {
      matcher: (ctx) => true,
      handler: createAuthMiddleware(async (ctx) => {
        // Access: ctx.context.returned (response data)
      }),
    },
  ],
}

Database Hooks

databaseHooks: {
  user: {
    create: {
      before: async ({ data }) => { /* add defaults, return false to block */ },
      after: async ({ data }) => { /* audit log, send welcome email */ },
    },
  },
  session: {
    create: {
      after: async ({ data, ctx }) => {
        await auditLog("session.created", {
          userId: data.userId,
          ip: ctx?.request?.headers.get("x-forwarded-for"),
        });
      },
    },
  },
}

Hook context (ctx.context): session, secret, authCookies, password.hash()/verify(), adapter, internalAdapter, generateId(), tables, baseURL.


Common Gotchas

  1. Model vs table name — Config uses ORM model name, not DB table name
  2. Plugin schema — Re-run CLI after adding plugins (always!)
  3. Secondary storage — Sessions go there by default, not DB
  4. Cookie cache — Custom session fields NOT cached, always re-fetched
  5. Stateless mode — No DB = session in cookie only, logout on cache expiry
  6. Change email flow — Sends to current email first, then new email
  7. 2FA + social — 2FA only works on credential accounts, not social-only
  8. Last owner — Cannot be removed from or leave an organization
  9. Rate limit memory — Memory storage resets on restart, bad for serverless

Production Security Checklist

  • BETTER_AUTH_SECRET set (32+ chars, high entropy)
  • BETTER_AUTH_URL uses HTTPS
  • trustedOrigins configured for all valid origins
  • Rate limiting enabled with appropriate per-endpoint limits
  • Rate limit storage set to "secondary-storage" or "database" (not memory)
  • CSRF protection enabled (disableCSRFCheck: false)
  • Secure cookies enabled (automatic with HTTPS)
  • account.encryptOAuthTokens: true if storing tokens
  • Background tasks configured for serverless (capture ExecutionContext from fetch handler)
  • Audit logging via databaseHooks or hooks
  • IP tracking headers configured if behind proxy
  • Email verification enabled
  • Password reset implemented
  • 2FA available for sensitive apps
  • Session expiry and refresh intervals reviewed
  • Cookie cache strategy chosen (jwe for sensitive session data)
  • account.accountLinking reviewed

Complete Production Config Example

import { betterAuth } from "better-auth";
import { twoFactor, organization } from "better-auth/plugins";

// Factory pattern — ctx comes from the Worker fetch handler
export function createAuth(env: Env, ctx: ExecutionContext) {
  return betterAuth({
    appName: "Grove",
    secret: env.BETTER_AUTH_SECRET,
    baseURL: "https://auth-api.grove.place",
    trustedOrigins: [
      "https://heartwood.grove.place",
      "https://*.grove.place",
    ],

    database: d1Adapter(env),
    secondaryStorage: kvAdapter(env),

    // Rate limiting (replaces custom threshold SDK for auth)
    rateLimit: {
      enabled: true,
      storage: "secondary-storage",
      customRules: {
        "/api/auth/sign-in/email": { window: 60, max: 5 },
        "/api/auth/sign-up/email": { window: 60, max: 3 },
        "/api/auth/change-password": { window: 60, max: 3 },
      },
    },

    // Sessions
    session: {
      expiresIn: 60 * 60 * 24 * 7,    // 7 days
      updateAge: 60 * 60 * 24,         // 24 hours
      freshAge: 60 * 60,               // 1 hour for sensitive actions
      cookieCache: {
        enabled: true,
        maxAge: 300,
        strategy: "jwe",
      },
    },

    // OAuth
    account: {
      encryptOAuthTokens: true,
      storeStateStrategy: "cookie",
    },

    // Security
    advanced: {
      useSecureCookies: true,
      crossSubDomainCookies: {
        enabled: true,
        domain: ".grove.place",
      },
      ipAddress: {
        ipAddressHeaders: ["x-forwarded-for"],
        ipv6Subnet: 64,
      },
      backgroundTasks: {
        handler: (promise) => ctx.waitUntil(promise),  // ctx captured from fetch handler
      },
    },

    // Plugins
    plugins: [
      twoFactor({
        issuer: "Grove",
        backupCodeOptions: { storeBackupCodes: "encrypted" },
      }),
      organization(),
    ],

    // Audit hooks
    databaseHooks: {
      session: {
        create: {
          after: async ({ data, ctx: hookCtx }) => {
            console.log(`[audit] session created: user=${data.userId}`);
          },
        },
      },
    },
  });
}

Resources

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

37.18%
按下载量换算83

Claude

28%
按下载量换算62

Cursor

18.54%
按下载量换算41

Gemini CLI

8.07%
按下载量换算18

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

敏感数据

该 Skill 可能接触密钥、Token、环境变量或敏感配置,应进入高风险复核队列,默认不自动发布。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills