Awesome-omni-skill better-auth

TypeScript authentication framework (framework-agnostic). Features: email/password, OAuth (Google, GitHub, Discord), 2FA (TOTP, SMS), passkeys/WebAuthn, session management, RBAC, rate limiting, database adapters. Actions: implement, configure, secure authentication systems. Keywords: Better Auth, authentication, authorization, OAuth, email/password, 2FA, MFA, TOTP, passkeys, WebAuthn, session management, RBAC, rate limiting, database adapter, TypeScript auth, social login, Google auth, GitHub auth, Discord auth, email verification, password reset. Use when: implementing TypeScript auth, adding OAuth providers, setting up 2FA/MFA, managing sessions, configuring RBAC, building secure auth systems.

install
source · Clone the upstream repo
git clone https://github.com/diegosouzapw/awesome-omni-skill
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/development/better-auth-hsnsaboor" ~/.claude/skills/diegosouzapw-awesome-omni-skill-better-auth-a7ca45 && rm -rf "$T"
manifest: skills/development/better-auth-hsnsaboor/SKILL.md
source content

Better Auth

Better Auth is a comprehensive, framework-agnostic authentication/authorization framework for TypeScript with built-in email/password, social OAuth, and powerful plugin ecosystem for advanced features.

Always consult better-auth.com/docs for code examples and latest API.

When to Use

  • Implementing auth in TypeScript/JavaScript applications
  • Adding email/password or social OAuth authentication
  • Setting up 2FA, passkeys, magic links, advanced auth features
  • Building multi-tenant apps with organization support
  • Managing sessions and user lifecycle
  • Working with any framework (Next.js, Nuxt, SvelteKit, Remix, Astro, Hono, Express, etc.)

Planning Phase (Before Implementation)

Before writing any code, gather requirements by scanning the project and asking the user structured questions.

Step 1: Scan the project

Analyze the codebase to auto-detect:

  • Framework — Look for
    next.config
    ,
    svelte.config
    ,
    nuxt.config
    ,
    astro.config
    ,
    vite.config
    , or Express/Hono entry files.
  • Database/ORM — Look for
    prisma/schema.prisma
    ,
    drizzle.config
    ,
    package.json
    deps (
    pg
    ,
    mysql2
    ,
    better-sqlite3
    ,
    mongoose
    ,
    mongodb
    ).
  • Existing auth — Look for existing auth libraries (
    next-auth
    ,
    lucia
    ,
    clerk
    ,
    supabase/auth
    ,
    firebase/auth
    ) in
    package.json
    or imports.
  • Package manager — Check for
    pnpm-lock.yaml
    ,
    yarn.lock
    ,
    bun.lockb
    , or
    package-lock.json
    .

Use what you find to pre-fill defaults and skip questions you can already answer.

Step 2: Ask planning questions

Ask the user all applicable questions (skip any already answered by the scan):

  1. Project type — New project | Adding auth to existing project | Migrating from another auth library
  2. Framework — Next.js (App/Pages Router) | SvelteKit | Nuxt | Astro | Express | Hono | SolidStart | Other
  3. Database & ORM — PostgreSQL/MySQL/SQLite + Prisma/Drizzle/direct driver | MongoDB
  4. Authentication methods (allow multiple) — Email & password | Social OAuth | Magic link | Passkey | Phone number
  5. Social providers (if OAuth selected) — Google | GitHub | Apple | Microsoft | Discord | Twitter/X
  6. Email verification (if email/password selected) — Yes | No
  7. Email provider (if verification or password reset needed) — Resend | Mock (console.log)
  8. Features & plugins (allow multiple) — 2FA | Organizations/teams | Admin dashboard | API bearer tokens | Password reset | None
  9. Auth pages — Sign in | Sign up | Forgot password | Reset password | Email verification
  10. Auth UI style — Minimal & clean | Centered card | Split layout | Floating/glassmorphism | Custom

Step 3: Summarize and confirm the plan

Present a concise implementation plan as a markdown checklist. Get user confirmation before proceeding.


Decision Tree

Is this a new/empty project?
├─ YES → New project setup
│   1. Identify framework
│   2. Choose database
│   3. Install better-auth
│   4. Create auth.ts + auth-client.ts
│   5. Set up route handler
│   6. Run CLI migrate/generate
│   7. Add features via plugins
│
├─ MIGRATING → Migration from existing auth
│   1. Audit current auth for gaps
│   2. Plan incremental migration
│   3. Install better-auth alongside existing auth
│   4. Migrate routes, then session logic, then UI
│   5. Remove old auth library
│   6. See migration guides in docs
│
└─ NO → Add auth to existing project
    1. Analyze project structure
    2. Install better-auth
    3. Create auth config
    4. Add route handler
    5. Run schema migrations
    6. Integrate into existing pages
    7. Add planned plugins and features

Quick Start

Installation

npm install better-auth
# or pnpm/yarn/bun add better-auth

Scoped packages (as needed):

PackageUse case
@better-auth/passkey
WebAuthn/Passkey auth
@better-auth/sso
SAML/OIDC enterprise SSO
@better-auth/stripe
Stripe payments
@better-auth/scim
SCIM user provisioning
@better-auth/expo
React Native/Expo

Environment Variables

BETTER_AUTH_SECRET=<32+ chars, generate with: openssl rand -base64 32>
BETTER_AUTH_URL=http://localhost:3000
DATABASE_URL=<your database connection string>

Add OAuth secrets as needed:

GITHUB_CLIENT_ID
,
GITHUB_CLIENT_SECRET
,
GOOGLE_CLIENT_ID
, etc.

Only define

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

CLI Commands

npx @better-auth/cli@latest migrate   # Apply schema (built-in adapter)
npx @better-auth/cli@latest generate  # Generate schema for Prisma/Drizzle
npx @better-auth/cli mcp --cursor     # Add MCP to AI tools

Re-run after adding/changing plugins.

File Location

CLI looks for

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


Server Config (auth.ts)

Location:

lib/auth.ts
or
src/lib/auth.ts

import { betterAuth } from "better-auth";

export const auth = betterAuth({
  database: {
    // See Database section below
  },
  emailAndPassword: {
    enabled: true,
    autoSignIn: true
  },
  socialProviders: {
    github: {
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    }
  }
});

// Export types
export type Session = typeof auth.$Infer.Session;

Core Config Options

OptionNotes
appName
Optional display name
baseURL
Only if
BETTER_AUTH_URL
not set
basePath
Default
/api/auth
. Set
/
for root.
secret
Only if
BETTER_AUTH_SECRET
not set
database
Required for most features. See adapters docs.
secondaryStorage
Redis/KV for sessions & rate limits
emailAndPassword
{ enabled: true }
to activate
socialProviders
{ google: { clientId, clientSecret }, ... }
plugins
Array of plugins
trustedOrigins
CSRF whitelist

Minimal config needs:

database
+
emailAndPassword: { enabled: true }
Standard config adds:
socialProviders
,
emailVerification.sendVerificationEmail
,
emailAndPassword.sendResetPassword
Full config adds:
plugins
,
session
,
account.accountLinking
,
rateLimit


Client Config (auth-client.ts)

import { createAuthClient } from "better-auth/client";

export const authClient = createAuthClient({
  baseURL: process.env.NEXT_PUBLIC_BETTER_AUTH_URL || "http://localhost:3000"
});

Import by framework:

FrameworkImport
React/Next.js
better-auth/react
Vue
better-auth/vue
Svelte
better-auth/svelte
Solid
better-auth/solid
Vanilla JS
better-auth/client

Client plugins go in

createAuthClient({ plugins: [...] })
.

Common exports:

signIn
,
signUp
,
signOut
,
useSession
,
getSession


Route Handler Setup

FrameworkFileHandler
Next.js App Router
app/api/auth/[...all]/route.ts
toNextJsHandler(auth)
→ export
{ GET, POST }
Next.js Pages
pages/api/auth/[...all].ts
toNextJsHandler(auth)
→ default export
ExpressAny file
app.all("/api/auth/*", toNodeHandler(auth))
SvelteKit
src/hooks.server.ts
svelteKitHandler(auth)
SolidStartRoute file
solidStartHandler(auth)
HonoRoute file
auth.handler(c.req.raw)

Next.js Server Components: Add

nextCookies()
plugin to auth config.


Database

Direct Connections

Pass

pg.Pool
,
mysql2
pool,
better-sqlite3
, or
bun:sqlite
instance directly.

ORM Adapters

Import from

better-auth/adapters/drizzle
,
better-auth/adapters/prisma
,
better-auth/adapters/mongodb
.

DatabaseSetup
SQLitePass
better-sqlite3
or
bun:sqlite
instance directly
PostgreSQLPass
pg.Pool
instance directly
MySQLPass
mysql2
pool directly
Prisma
prismaAdapter(prisma, { provider: "postgresql" })
from
better-auth/adapters/prisma
Drizzle
drizzleAdapter(db, { provider: "pg" })
from
better-auth/adapters/drizzle
MongoDB
mongodbAdapter(db)
from
better-auth/adapters/mongodb

Critical: 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"
.

Database Migrations

AdapterCommand
Built-in Kysely
npx @better-auth/cli@latest migrate
(applies directly)
Prisma
npx @better-auth/cli@latest generate --output prisma/schema.prisma
then
npx prisma migrate dev
Drizzle
npx @better-auth/cli@latest generate --output src/db/auth-schema.ts
then
npx drizzle-kit push

Re-run after adding plugins.


Basic Usage

// Sign up
await authClient.signUp.email({
  email: "user@example.com",
  password: "secure123",
  name: "John Doe"
});

// Sign in
await authClient.signIn.email({
  email: "user@example.com",
  password: "secure123"
});

// OAuth
await authClient.signIn.social({ provider: "github" });

// Session (client)
const { data: session } = authClient.useSession(); // React/Vue/Svelte
const { data: session } = await authClient.getSession(); // Vanilla JS

// Session (server)
const session = await auth.api.getSession({ headers: await headers() });

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

Cookie cache strategies:

  • compact
    (default) - Base64url + HMAC. Smallest.
  • jwt
    - Standard JWT. Readable but signed.
  • jwe
    - Encrypted. Maximum security.

Key options:

session.expiresIn
(default 7 days),
session.updateAge
(refresh interval),
session.cookieCache.maxAge
,
session.cookieCache.version
(change to invalidate all sessions).


User & Account Config

User:

user.modelName
,
user.fields
(column mapping),
user.additionalFields
,
user.changeEmail.enabled
(disabled by default),
user.deleteUser.enabled
(disabled by default).

Account:

account.modelName
,
account.accountLinking.enabled
,
account.storeAccountCookie
(for stateless OAuth).

Required for registration:

email
and
name
fields.


Email Flows

  • emailVerification.sendVerificationEmail
    - Must be defined for verification to work
  • emailVerification.sendOnSignUp
    /
    sendOnSignIn
    - Auto-send triggers
  • emailAndPassword.sendResetPassword
    - Password reset email handler

Feature Selection Matrix

FeaturePlugin RequiredUse CaseReference
Email/PasswordNo (built-in)Basic authemail-password-auth.md
OAuth (GitHub, Google, etc.)No (built-in)Social loginoauth-providers.md
Email VerificationNo (built-in)Verify email addressesemail-password-auth.md
Password ResetNo (built-in)Forgot password flowemail-password-auth.md
Two-Factor Auth (2FA/TOTP)Yes (
twoFactor
)
Enhanced securityadvanced-features.md
Passkeys/WebAuthnYes (
passkey
)
Passwordless authadvanced-features.md
Magic LinkYes (
magicLink
)
Email-based loginadvanced-features.md
Username AuthYes (
username
)
Username loginemail-password-auth.md
Organizations/Multi-tenantYes (
organization
)
Team/org featuresadvanced-features.md
Rate LimitingNo (built-in)Prevent abuseadvanced-features.md
Session ManagementNo (built-in)User sessionsadvanced-features.md

Auth Method Selection Guide

Choose Email/Password when:

  • Building standard web app with traditional auth
  • Need full control over user credentials
  • Targeting users who prefer email-based accounts

Choose OAuth when:

  • Want quick signup with minimal friction
  • Users already have social accounts
  • Need access to social profile data

Choose Passkeys when:

  • Want passwordless experience
  • Targeting modern browsers/devices
  • Security is top priority

Choose Magic Link when:

  • Want passwordless without WebAuthn complexity
  • Targeting email-first users
  • Need temporary access links

Combine Multiple Methods when:

  • Want flexibility for different user preferences
  • Building enterprise apps with various auth requirements
  • Need progressive enhancement (start simple, add more options)

Plugins

Import from dedicated paths for tree-shaking:

import { twoFactor } from "better-auth/plugins/two-factor"

NOT

from "better-auth/plugins"
.

Common Plugins

PluginServer ImportClient ImportPurpose
twoFactor
better-auth/plugins
twoFactorClient
2FA with TOTP/OTP
organization
better-auth/plugins
organizationClient
Teams/orgs
admin
better-auth/plugins
adminClient
User management
bearer
better-auth/plugins
-API token auth
openAPI
better-auth/plugins
-API docs
passkey
@better-auth/passkey
passkeyClient
WebAuthn
sso
@better-auth/sso
-Enterprise SSO

Popular plugins:

twoFactor
,
organization
,
passkey
,
magicLink
,
emailOtp
,
username
,
phoneNumber
,
admin
,
apiKey
,
bearer
,
jwt
,
multiSession
,
sso
,
oauthProvider
,
oidcProvider
,
openAPI
,
genericOAuth
.

Plugin pattern: Server plugin + client plugin + run migrations.

Client plugins go in

createAuthClient({ plugins: [...] })
.


Security

Config (
advanced
section)

  • useSecureCookies
    - Force HTTPS cookies
  • disableCSRFCheck
    - Security risk
  • disableOriginCheck
    - Security risk
  • crossSubDomainCookies.enabled
    - Share cookies across subdomains
  • ipAddress.ipAddressHeaders
    - Custom IP headers for proxies
  • database.generateId
    - Custom ID generation or
    "serial"
    /
    "uuid"
    /
    false

Rate limiting:

rateLimit.enabled
,
rateLimit.window
,
rateLimit.max
,
rateLimit.storage
("memory" | "database" | "secondary-storage").

Security Checklist

  • BETTER_AUTH_SECRET
    set (32+ chars)
  • advanced.useSecureCookies: true
    in production
  • trustedOrigins
    configured
  • Rate limits enabled
  • Email verification enabled
  • Password reset implemented
  • 2FA for sensitive apps
  • CSRF protection NOT disabled
  • account.accountLinking
    reviewed

Hooks

Endpoint hooks:

hooks.before
/
hooks.after
- Array of
{ matcher, handler }
. Use
createAuthMiddleware
. Access
ctx.path
,
ctx.context.returned
(after),
ctx.context.session
.

Database hooks:

databaseHooks.user.create.before/after
, same for
session
,
account
. Useful for adding default values or post-creation actions.

Hook context (

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


Type Safety

Infer types:

typeof auth.$Infer.Session
,
typeof auth.$Infer.Session.user
.

For separate client/server projects:

createAuthClient<typeof auth>()
.


Auth UI Implementation

Sign in flow:

  1. signIn.email({ email, password })
    or
    signIn.social({ provider, callbackURL })
  2. Handle
    error
    in response
  3. Redirect on success

Session check (client):

useSession()
hook returns
{ data: session, isPending }

Session check (server):

auth.api.getSession({ headers: await headers() })

Protected routes: Check session, redirect to

/sign-in
if null.


Core Architecture

Better Auth uses client-server architecture:

  1. Server (
    better-auth
    ): Handles auth logic, database ops, API routes
  2. Client (
    better-auth/client
    ): Provides hooks/methods for frontend
  3. Plugins: Extend both server/client functionality

Implementation Checklist

  • Install
    better-auth
    package
  • Set environment variables (SECRET, URL)
  • Create auth server instance with database config
  • Run schema migration (
    npx @better-auth/cli generate
    )
  • Mount API handler in framework
  • Create client instance
  • Implement sign-up/sign-in UI
  • Add session management to components
  • Set up protected routes/middleware
  • Add plugins as needed (regenerate schema after)
  • Test complete auth flow
  • Configure email sending (verification/reset)
  • Enable rate limiting for production
  • Set up error handling

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
  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

Troubleshooting

IssueFix
"Secret not set"Add
BETTER_AUTH_SECRET
env var
"Invalid Origin"Add domain to
trustedOrigins
Cookies not settingCheck
baseURL
matches domain; enable secure cookies in prod
OAuth callback errorsVerify redirect URIs in provider dashboard
Type errors after adding pluginRe-run CLI generate/migrate

Reference Documentation

Core Authentication

Advanced Features

  • Advanced Features - 2FA/MFA, passkeys, magic links, organizations, rate limiting, session management

Scripts

  • scripts/better_auth_init.py
    - Initialize Better Auth configuration with interactive setup

Resources