Claude Cursor opencode Skill

better-auth

Implement authentication and authorization with Better Auth - a framework-agnostic TypeScript authentication framework. Features include email/password authentication with verification, OAuth providers (Google, GitHub, Discord, etc.), two-factor authentication (TOTP, SMS), passke

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download vodailocz-kilo-kit-mcp-skills_engineering_better-auth-0448e6c.zip · 22 KB
Part of vodailocz/kilo-kit-mcp — 142 skills

Install

skills CLI npx skills add https://github.com/VoDaiLocz/kilo-kit-mcp/tree/main/skills/engineering/better-auth
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install vodailocz-kilo-kit-mcp@llmmart
Git git clone https://github.com/VoDaiLocz/kilo-kit-mcp.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole vodailocz/kilo-kit-mcp collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Better Auth Skill

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

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

Quick Start

Installation

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

Environment Setup

Create .env:

BETTER_AUTH_SECRET=<generated-secret-32-chars-min>
BETTER_AUTH_URL=http://localhost:3000

Basic Server Setup

Create auth.ts (root, lib/, utils/, or under src/app/server/):

import { betterAuth } from "better-auth";

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

Database Schema

npx @better-auth/cli generate  # Generate schema/migrations
npx @better-auth/cli migrate   # Apply migrations (Kysely only)

Mount API Handler

Next.js App Router:

// app/api/auth/[...all]/route.ts
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";

export const { POST, GET } = toNextJsHandler(auth);

Other frameworks: See references/email-password-auth.md#framework-setup

Client Setup

Create auth-client.ts:

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

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

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
const { data: session } = authClient.useSession(); // React/Vue/Svelte
const { data: session } = await authClient.getSession(); // Vanilla JS

Feature Selection Matrix

Feature Plugin Required Use Case Reference
Email/Password No (built-in) Basic auth email-password-auth.md
OAuth (GitHub, Google, etc.) No (built-in) Social login oauth-providers.md
Email Verification No (built-in) Verify email addresses email-password-auth.md
Password Reset No (built-in) Forgot password flow email-password-auth.md
Two-Factor Auth (2FA/TOTP) Yes (twoFactor) Enhanced security advanced-features.md
Passkeys/WebAuthn Yes (passkey) Passwordless auth advanced-features.md
Magic Link Yes (magicLink) Email-based login advanced-features.md
Username Auth Yes (username) Username login email-password-auth.md
Organizations/Multi-tenant Yes (organization) Team/org features advanced-features.md
Rate Limiting No (built-in) Prevent abuse advanced-features.md
Session Management No (built-in) User sessions advanced-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)

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

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

Files (kilo-kit-mcp)
  • references
    • advanced-features.md 10.4 KB
      # Advanced Features
      
      Better Auth plugins extend functionality beyond basic authentication.
      
      ## Two-Factor Authentication
      
      ### Server Setup
      
      ```ts
      import { betterAuth } from "better-auth";
      import { twoFactor } from "better-auth/plugins";
      
      export const auth = betterAuth({
        plugins: [
          twoFactor({
            issuer: "YourAppName", // TOTP issuer name
            otpOptions: {
              period: 30, // OTP validity period (seconds)
              digits: 6, // OTP length
            }
          })
        ]
      });
      ```
      
      ### Client Setup
      
      ```ts
      import { createAuthClient } from "better-auth/client";
      import { twoFactorClient } from "better-auth/client/plugins";
      
      export const authClient = createAuthClient({
        plugins: [
          twoFactorClient({
            twoFactorPage: "/two-factor", // Redirect to 2FA verification page
            redirect: true // Auto-redirect if 2FA required
          })
        ]
      });
      ```
      
      ### Enable 2FA for User
      
      ```ts
      // Enable TOTP
      const { data } = await authClient.twoFactor.enable({
        password: "userPassword" // Verify user identity
      });
      
      // data contains QR code URI for authenticator app
      const qrCodeUri = data.totpURI;
      const backupCodes = data.backupCodes; // Save these securely
      ```
      
      ### Verify TOTP Code
      
      ```ts
      await authClient.twoFactor.verifyTOTP({
        code: "123456",
        trustDevice: true // Skip 2FA on this device for 30 days
      });
      ```
      
      ### Disable 2FA
      
      ```ts
      await authClient.twoFactor.disable({
        password: "userPassword"
      });
      ```
      
      ### Backup Codes
      
      ```ts
      // Generate new backup codes
      const { data } = await authClient.twoFactor.generateBackupCodes({
        password: "userPassword"
      });
      
      // Use backup code instead of TOTP
      await authClient.twoFactor.verifyBackupCode({
        code: "backup-code-123"
      });
      ```
      
      ## Passkeys (WebAuthn)
      
      ### Server Setup
      
      ```ts
      import { betterAuth } from "better-auth";
      import { passkey } from "better-auth/plugins";
      
      export const auth = betterAuth({
        plugins: [
          passkey({
            rpName: "YourApp", // Relying Party name
            rpID: "yourdomain.com" // Your domain
          })
        ]
      });
      ```
      
      ### Client Setup
      
      ```ts
      import { createAuthClient } from "better-auth/client";
      import { passkeyClient } from "better-auth/client/plugins";
      
      export const authClient = createAuthClient({
        plugins: [passkeyClient()]
      });
      ```
      
      ### Register Passkey
      
      ```ts
      // User must be authenticated first
      await authClient.passkey.register({
        name: "My Laptop" // Optional: name for this passkey
      });
      ```
      
      ### Sign In with Passkey
      
      ```ts
      await authClient.passkey.signIn();
      ```
      
      ### List User Passkeys
      
      ```ts
      const { data } = await authClient.passkey.list();
      // data contains array of registered passkeys
      ```
      
      ### Delete Passkey
      
      ```ts
      await authClient.passkey.delete({
        id: "passkey-id"
      });
      ```
      
      ## Magic Link
      
      ### Server Setup
      
      ```ts
      import { betterAuth } from "better-auth";
      import { magicLink } from "better-auth/plugins";
      
      export const auth = betterAuth({
        plugins: [
          magicLink({
            sendMagicLink: async ({ email, url, token }) => {
              await sendEmail({
                to: email,
                subject: "Sign in to YourApp",
                html: `Click <a href="${url}">here</a> to sign in.`
              });
            },
            expiresIn: 300, // Link expires in 5 minutes (seconds)
          })
        ]
      });
      ```
      
      ### Client Setup
      
      ```ts
      import { createAuthClient } from "better-auth/client";
      import { magicLinkClient } from "better-auth/client/plugins";
      
      export const authClient = createAuthClient({
        plugins: [magicLinkClient()]
      });
      ```
      
      ### Send Magic Link
      
      ```ts
      await authClient.magicLink.sendMagicLink({
        email: "user@example.com",
        callbackURL: "/dashboard"
      });
      ```
      
      ### Verify Magic Link
      
      ```ts
      // Called automatically when user clicks link
      // Token in URL query params handled by Better Auth
      await authClient.magicLink.verify({
        token: "token-from-url"
      });
      ```
      
      ## Organizations (Multi-Tenancy)
      
      ### Server Setup
      
      ```ts
      import { betterAuth } from "better-auth";
      import { organization } from "better-auth/plugins";
      
      export const auth = betterAuth({
        plugins: [
          organization({
            allowUserToCreateOrganization: true,
            organizationLimit: 5, // Max orgs per user
            creatorRole: "owner" // Role for org creator
          })
        ]
      });
      ```
      
      ### Client Setup
      
      ```ts
      import { createAuthClient } from "better-auth/client";
      import { organizationClient } from "better-auth/client/plugins";
      
      export const authClient = createAuthClient({
        plugins: [organizationClient()]
      });
      ```
      
      ### Create Organization
      
      ```ts
      await authClient.organization.create({
        name: "Acme Corp",
        slug: "acme", // Unique slug
        metadata: {
          industry: "Technology"
        }
      });
      ```
      
      ### Invite Members
      
      ```ts
      await authClient.organization.inviteMember({
        organizationId: "org-id",
        email: "user@example.com",
        role: "member", // owner, admin, member
        message: "Join our team!" // Optional
      });
      ```
      
      ### Accept Invitation
      
      ```ts
      await authClient.organization.acceptInvitation({
        invitationId: "invitation-id"
      });
      ```
      
      ### List Organizations
      
      ```ts
      const { data } = await authClient.organization.list();
      // Returns user's organizations
      ```
      
      ### Update Member Role
      
      ```ts
      await authClient.organization.updateMemberRole({
        organizationId: "org-id",
        userId: "user-id",
        role: "admin"
      });
      ```
      
      ### Remove Member
      
      ```ts
      await authClient.organization.removeMember({
        organizationId: "org-id",
        userId: "user-id"
      });
      ```
      
      ### Delete Organization
      
      ```ts
      await authClient.organization.delete({
        organizationId: "org-id"
      });
      ```
      
      ## Session Management
      
      ### Configure Session Expiration
      
      ```ts
      export const auth = betterAuth({
        session: {
          expiresIn: 60 * 60 * 24 * 7, // 7 days (seconds)
          updateAge: 60 * 60 * 24, // Update session every 24 hours
          cookieCache: {
            enabled: true,
            maxAge: 5 * 60 // Cache for 5 minutes
          }
        }
      });
      ```
      
      ### Server-Side Session
      
      ```ts
      // Next.js
      import { auth } from "@/lib/auth";
      import { headers } from "next/headers";
      
      const session = await auth.api.getSession({
        headers: await headers()
      });
      
      if (!session) {
        // Not authenticated
      }
      ```
      
      ### Client-Side Session
      
      ```tsx
      // React
      import { authClient } from "@/lib/auth-client";
      
      function UserProfile() {
        const { data: session, isPending, error } = authClient.useSession();
      
        if (isPending) return <div>Loading...</div>;
        if (error) return <div>Error</div>;
        if (!session) return <div>Not logged in</div>;
      
        return <div>Hello, {session.user.name}!</div>;
      }
      ```
      
      ### List Active Sessions
      
      ```ts
      const { data: sessions } = await authClient.listSessions();
      // Returns all active sessions for current user
      ```
      
      ### Revoke Session
      
      ```ts
      await authClient.revokeSession({
        sessionId: "session-id"
      });
      ```
      
      ### Revoke All Sessions
      
      ```ts
      await authClient.revokeAllSessions();
      ```
      
      ## Rate Limiting
      
      ### Server Configuration
      
      ```ts
      export const auth = betterAuth({
        rateLimit: {
          enabled: true,
          window: 60, // Time window in seconds
          max: 10, // Max requests per window
          storage: "memory", // "memory" or "database"
          customRules: {
            "/api/auth/sign-in": {
              window: 60,
              max: 5 // Stricter limit for sign-in
            },
            "/api/auth/sign-up": {
              window: 3600,
              max: 3 // 3 signups per hour
            }
          }
        }
      });
      ```
      
      ### Custom Rate Limiter
      
      ```ts
      import { betterAuth } from "better-auth";
      
      export const auth = betterAuth({
        rateLimit: {
          enabled: true,
          customLimiter: async ({ request, limit }) => {
            // Custom rate limiting logic
            const ip = request.headers.get("x-forwarded-for");
            const key = `ratelimit:${ip}`;
      
            // Use Redis, etc.
            const count = await redis.incr(key);
            if (count === 1) {
              await redis.expire(key, limit.window);
            }
      
            if (count > limit.max) {
              throw new Error("Rate limit exceeded");
            }
          }
        }
      });
      ```
      
      ## Anonymous Sessions
      
      Track users before they sign up.
      
      ### Server Setup
      
      ```ts
      import { betterAuth } from "better-auth";
      import { anonymous } from "better-auth/plugins";
      
      export const auth = betterAuth({
        plugins: [anonymous()]
      });
      ```
      
      ### Client Usage
      
      ```ts
      // Create anonymous session
      const { data } = await authClient.signIn.anonymous();
      
      // Convert to full account
      await authClient.signUp.email({
        email: "user@example.com",
        password: "password123",
        linkAnonymousSession: true // Link anonymous data
      });
      ```
      
      ## Email OTP
      
      One-time password via email (passwordless).
      
      ### Server Setup
      
      ```ts
      import { betterAuth } from "better-auth";
      import { emailOTP } from "better-auth/plugins";
      
      export const auth = betterAuth({
        plugins: [
          emailOTP({
            sendVerificationOTP: async ({ email, otp }) => {
              await sendEmail({
                to: email,
                subject: "Your verification code",
                text: `Your code is: ${otp}`
              });
            },
            expiresIn: 300, // 5 minutes
            length: 6 // OTP length
          })
        ]
      });
      ```
      
      ### Client Usage
      
      ```ts
      // Send OTP to email
      await authClient.emailOTP.sendOTP({
        email: "user@example.com"
      });
      
      // Verify OTP
      await authClient.emailOTP.verifyOTP({
        email: "user@example.com",
        otp: "123456"
      });
      ```
      
      ## Phone Number Authentication
      
      Requires phone number plugin.
      
      ### Server Setup
      
      ```ts
      import { betterAuth } from "better-auth";
      import { phoneNumber } from "better-auth/plugins";
      
      export const auth = betterAuth({
        plugins: [
          phoneNumber({
            sendOTP: async ({ phoneNumber, otp }) => {
              // Use Twilio, AWS SNS, etc.
              await sendSMS(phoneNumber, `Your code: ${otp}`);
            }
          })
        ]
      });
      ```
      
      ### Client Usage
      
      ```ts
      // Sign up with phone
      await authClient.signUp.phoneNumber({
        phoneNumber: "+1234567890",
        password: "password123"
      });
      
      // Send OTP
      await authClient.phoneNumber.sendOTP({
        phoneNumber: "+1234567890"
      });
      
      // Verify OTP
      await authClient.phoneNumber.verifyOTP({
        phoneNumber: "+1234567890",
        otp: "123456"
      });
      ```
      
      ## Best Practices
      
      1. **2FA**: Offer 2FA as optional, make mandatory for admin users
      2. **Passkeys**: Implement as progressive enhancement (fallback to password)
      3. **Magic Links**: Set short expiration (5-15 minutes)
      4. **Organizations**: Implement RBAC for org permissions
      5. **Sessions**: Use short expiration for sensitive apps
      6. **Rate Limiting**: Enable in production, adjust limits based on usage
      7. **Anonymous Sessions**: Clean up old anonymous sessions periodically
      8. **Backup Codes**: Force users to save backup codes before enabling 2FA
      9. **Multi-Device**: Allow users to manage trusted devices
      10. **Audit Logs**: Track sensitive operations (role changes, 2FA changes)
      
      ## Regenerate Schema After Plugins
      
      After adding any plugin:
      
      ```bash
      npx @better-auth/cli generate
      npx @better-auth/cli migrate # if using Kysely
      ```
      
      Or manually apply migrations for your ORM (Drizzle, Prisma).
      
    • database-integration.md 10.3 KB
      # Database Integration
      
      Better Auth supports multiple databases and ORMs for flexible data persistence.
      
      ## Supported Databases
      
      - SQLite
      - PostgreSQL
      - MySQL/MariaDB
      - MongoDB
      - Any database with adapter support
      
      ## Direct Database Connection
      
      ### SQLite
      
      ```ts
      import { betterAuth } from "better-auth";
      import Database from "better-sqlite3";
      
      export const auth = betterAuth({
        database: new Database("./sqlite.db"),
        // or
        database: new Database(":memory:") // In-memory for testing
      });
      ```
      
      ### PostgreSQL
      
      ```ts
      import { betterAuth } from "better-auth";
      import { Pool } from "pg";
      
      const pool = new Pool({
        connectionString: process.env.DATABASE_URL,
        // or explicit config
        host: "localhost",
        port: 5432,
        user: "postgres",
        password: "password",
        database: "myapp"
      });
      
      export const auth = betterAuth({
        database: pool
      });
      ```
      
      ### MySQL
      
      ```ts
      import { betterAuth } from "better-auth";
      import { createPool } from "mysql2/promise";
      
      const pool = createPool({
        host: "localhost",
        user: "root",
        password: "password",
        database: "myapp",
        waitForConnections: true,
        connectionLimit: 10
      });
      
      export const auth = betterAuth({
        database: pool
      });
      ```
      
      ## ORM Adapters
      
      ### Drizzle ORM
      
      **Install:**
      ```bash
      npm install drizzle-orm better-auth
      ```
      
      **Setup:**
      ```ts
      import { betterAuth } from "better-auth";
      import { drizzleAdapter } from "better-auth/adapters/drizzle";
      import { drizzle } from "drizzle-orm/node-postgres";
      import { Pool } from "pg";
      
      const pool = new Pool({
        connectionString: process.env.DATABASE_URL
      });
      
      const db = drizzle(pool);
      
      export const auth = betterAuth({
        database: drizzleAdapter(db, {
          provider: "pg", // "pg" | "mysql" | "sqlite"
          schema: {
            // Optional: custom table names
            user: "users",
            session: "sessions",
            account: "accounts",
            verification: "verifications"
          }
        })
      });
      ```
      
      **Generate Schema:**
      ```bash
      npx @better-auth/cli generate --adapter drizzle
      ```
      
      ### Prisma
      
      **Install:**
      ```bash
      npm install @prisma/client better-auth
      ```
      
      **Setup:**
      ```ts
      import { betterAuth } from "better-auth";
      import { prismaAdapter } from "better-auth/adapters/prisma";
      import { PrismaClient } from "@prisma/client";
      
      const prisma = new PrismaClient();
      
      export const auth = betterAuth({
        database: prismaAdapter(prisma, {
          provider: "postgresql", // "postgresql" | "mysql" | "sqlite"
        })
      });
      ```
      
      **Generate Schema:**
      ```bash
      npx @better-auth/cli generate --adapter prisma
      ```
      
      **Apply to Prisma:**
      ```bash
      # Add generated schema to schema.prisma
      npx prisma migrate dev --name init
      npx prisma generate
      ```
      
      ### Kysely
      
      **Install:**
      ```bash
      npm install kysely better-auth
      ```
      
      **Setup:**
      ```ts
      import { betterAuth } from "better-auth";
      import { kyselyAdapter } from "better-auth/adapters/kysely";
      import { Kysely, PostgresDialect } from "kysely";
      import { Pool } from "pg";
      
      const db = new Kysely({
        dialect: new PostgresDialect({
          pool: new Pool({
            connectionString: process.env.DATABASE_URL
          })
        })
      });
      
      export const auth = betterAuth({
        database: kyselyAdapter(db, {
          provider: "pg"
        })
      });
      ```
      
      **Auto-migrate with Kysely:**
      ```bash
      npx @better-auth/cli migrate --adapter kysely
      ```
      
      ### MongoDB
      
      **Install:**
      ```bash
      npm install mongodb better-auth
      ```
      
      **Setup:**
      ```ts
      import { betterAuth } from "better-auth";
      import { mongodbAdapter } from "better-auth/adapters/mongodb";
      import { MongoClient } from "mongodb";
      
      const client = new MongoClient(process.env.MONGODB_URI!);
      await client.connect();
      
      export const auth = betterAuth({
        database: mongodbAdapter(client, {
          databaseName: "myapp"
        })
      });
      ```
      
      **Generate Collections:**
      ```bash
      npx @better-auth/cli generate --adapter mongodb
      ```
      
      ## Core Database Schema
      
      Better Auth requires these core tables/collections:
      
      ### User Table
      
      ```sql
      CREATE TABLE user (
        id TEXT PRIMARY KEY,
        email TEXT UNIQUE NOT NULL,
        emailVerified BOOLEAN DEFAULT FALSE,
        name TEXT,
        image TEXT,
        createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
      );
      ```
      
      ### Session Table
      
      ```sql
      CREATE TABLE session (
        id TEXT PRIMARY KEY,
        userId TEXT NOT NULL,
        expiresAt TIMESTAMP NOT NULL,
        ipAddress TEXT,
        userAgent TEXT,
        createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        FOREIGN KEY (userId) REFERENCES user(id) ON DELETE CASCADE
      );
      ```
      
      ### Account Table
      
      ```sql
      CREATE TABLE account (
        id TEXT PRIMARY KEY,
        userId TEXT NOT NULL,
        accountId TEXT NOT NULL,
        providerId TEXT NOT NULL,
        accessToken TEXT,
        refreshToken TEXT,
        expiresAt TIMESTAMP,
        scope TEXT,
        createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        FOREIGN KEY (userId) REFERENCES user(id) ON DELETE CASCADE,
        UNIQUE(providerId, accountId)
      );
      ```
      
      ### Verification Table
      
      ```sql
      CREATE TABLE verification (
        id TEXT PRIMARY KEY,
        identifier TEXT NOT NULL,
        value TEXT NOT NULL,
        expiresAt TIMESTAMP NOT NULL,
        createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
      );
      ```
      
      ## Schema Generation
      
      ### Using CLI
      
      ```bash
      # Generate schema files
      npx @better-auth/cli generate
      
      # Specify adapter
      npx @better-auth/cli generate --adapter drizzle
      npx @better-auth/cli generate --adapter prisma
      
      # Specify output
      npx @better-auth/cli generate --output ./db/schema.ts
      ```
      
      ### Auto-migrate (Kysely only)
      
      ```bash
      npx @better-auth/cli migrate
      ```
      
      For other ORMs, apply generated schema manually.
      
      ## Custom Fields
      
      Add custom fields to user table:
      
      ```ts
      export const auth = betterAuth({
        user: {
          additionalFields: {
            role: {
              type: "string",
              required: false,
              defaultValue: "user"
            },
            phoneNumber: {
              type: "string",
              required: false
            },
            subscriptionTier: {
              type: "string",
              required: false
            }
          }
        }
      });
      ```
      
      After adding fields:
      ```bash
      npx @better-auth/cli generate
      ```
      
      Update user with custom fields:
      ```ts
      await authClient.updateUser({
        role: "admin",
        phoneNumber: "+1234567890"
      });
      ```
      
      ## Plugin Schema Extensions
      
      Plugins add their own tables/fields. Regenerate schema after adding plugins:
      
      ```bash
      npx @better-auth/cli generate
      ```
      
      ### Two-Factor Plugin Tables
      
      - `twoFactor`: Stores TOTP secrets, backup codes
      
      ### Passkey Plugin Tables
      
      - `passkey`: Stores WebAuthn credentials
      
      ### Organization Plugin Tables
      
      - `organization`: Organization data
      - `member`: Organization members
      - `invitation`: Pending invitations
      
      ## Migration Strategies
      
      ### Development
      
      ```bash
      # Generate schema
      npx @better-auth/cli generate
      
      # Apply migrations (Kysely)
      npx @better-auth/cli migrate
      
      # Or manual (Prisma)
      npx prisma migrate dev
      
      # Or manual (Drizzle)
      npx drizzle-kit push
      ```
      
      ### Production
      
      ```bash
      # Review generated migration
      npx @better-auth/cli generate
      
      # Test in staging
      # Apply to production with your ORM's migration tool
      
      # Prisma
      npx prisma migrate deploy
      
      # Drizzle
      npx drizzle-kit push
      
      # Kysely
      npx @better-auth/cli migrate
      ```
      
      ## Connection Pooling
      
      ### PostgreSQL
      
      ```ts
      import { Pool } from "pg";
      
      const pool = new Pool({
        connectionString: process.env.DATABASE_URL,
        max: 20, // Max connections
        idleTimeoutMillis: 30000,
        connectionTimeoutMillis: 2000,
      });
      ```
      
      ### MySQL
      
      ```ts
      import { createPool } from "mysql2/promise";
      
      const pool = createPool({
        connectionString: process.env.DATABASE_URL,
        waitForConnections: true,
        connectionLimit: 10,
        queueLimit: 0
      });
      ```
      
      ## Database URLs
      
      ### PostgreSQL
      
      ```env
      DATABASE_URL=postgresql://user:password@localhost:5432/dbname
      # Or with connection params
      DATABASE_URL=postgresql://user:password@localhost:5432/dbname?schema=public&connection_limit=10
      ```
      
      ### MySQL
      
      ```env
      DATABASE_URL=mysql://user:password@localhost:3306/dbname
      ```
      
      ### SQLite
      
      ```env
      DATABASE_URL=file:./dev.db
      # Or in-memory
      DATABASE_URL=:memory:
      ```
      
      ### MongoDB
      
      ```env
      MONGODB_URI=mongodb://localhost:27017/dbname
      # Or Atlas
      MONGODB_URI=mongodb+srv://user:password@cluster.mongodb.net/dbname
      ```
      
      ## Performance Optimization
      
      ### Indexes
      
      Better Auth CLI auto-generates essential indexes:
      - `user.email` (unique)
      - `session.userId`
      - `account.userId`
      - `account.providerId, accountId` (unique)
      
      Add custom indexes for performance:
      ```sql
      CREATE INDEX idx_session_expires ON session(expiresAt);
      CREATE INDEX idx_user_created ON user(createdAt);
      ```
      
      ### Query Optimization
      
      ```ts
      // Use connection pooling
      // Enable query caching where applicable
      // Monitor slow queries
      
      export const auth = betterAuth({
        advanced: {
          defaultCookieAttributes: {
            sameSite: "lax",
            secure: true,
            httpOnly: true
          }
        }
      });
      ```
      
      ## Backup Strategies
      
      ### PostgreSQL
      
      ```bash
      # Backup
      pg_dump dbname > backup.sql
      
      # Restore
      psql dbname < backup.sql
      ```
      
      ### MySQL
      
      ```bash
      # Backup
      mysqldump -u root -p dbname > backup.sql
      
      # Restore
      mysql -u root -p dbname < backup.sql
      ```
      
      ### SQLite
      
      ```bash
      # Copy file
      cp dev.db dev.db.backup
      
      # Or use backup command
      sqlite3 dev.db ".backup backup.db"
      ```
      
      ### MongoDB
      
      ```bash
      # Backup
      mongodump --db=dbname --out=./backup
      
      # Restore
      mongorestore --db=dbname ./backup/dbname
      ```
      
      ## Best Practices
      
      1. **Environment Variables**: Store credentials in env vars, never commit
      2. **Connection Pooling**: Use pools for PostgreSQL/MySQL in production
      3. **Migrations**: Use ORM migration tools, not raw SQL in production
      4. **Indexes**: Add indexes for frequently queried fields
      5. **Backups**: Automate daily backups in production
      6. **SSL**: Use SSL/TLS for database connections in production
      7. **Schema Sync**: Keep schema in sync across environments
      8. **Testing**: Use separate database for tests (in-memory SQLite ideal)
      9. **Monitoring**: Monitor query performance and connection pool usage
      10. **Cleanup**: Periodically clean expired sessions/verifications
      
      ## Troubleshooting
      
      ### Connection Errors
      
      ```ts
      // Add connection timeout
      const pool = new Pool({
        connectionString: process.env.DATABASE_URL,
        connectionTimeoutMillis: 5000
      });
      ```
      
      ### Schema Mismatch
      
      ```bash
      # Regenerate schema
      npx @better-auth/cli generate
      
      # Apply migrations
      # For Prisma: npx prisma migrate dev
      # For Drizzle: npx drizzle-kit push
      ```
      
      ### Migration Failures
      
      - Check database credentials
      - Verify database server is running
      - Check for schema conflicts
      - Review migration SQL manually
      
      ### Performance Issues
      
      - Add indexes on foreign keys
      - Enable connection pooling
      - Monitor slow queries
      - Consider read replicas for heavy read workloads
      
    • email-password-auth.md 8.4 KB
      # Email/Password Authentication
      
      Email/password is built-in auth method in Better Auth. No plugins required for basic functionality.
      
      ## Server Configuration
      
      ### Basic Setup
      
      ```ts
      import { betterAuth } from "better-auth";
      
      export const auth = betterAuth({
        emailAndPassword: {
          enabled: true,
          autoSignIn: true, // Auto sign-in after signup (default: true)
          requireEmailVerification: false, // Require email verification before login
          sendResetPasswordToken: async ({ user, url }) => {
            // Send password reset email
            await sendEmail(user.email, url);
          }
        }
      });
      ```
      
      ### Custom Password Requirements
      
      ```ts
      export const auth = betterAuth({
        emailAndPassword: {
          enabled: true,
          password: {
            minLength: 8,
            requireUppercase: true,
            requireLowercase: true,
            requireNumbers: true,
            requireSpecialChars: true
          }
        }
      });
      ```
      
      ## Client Usage
      
      ### Sign Up
      
      ```ts
      import { authClient } from "@/lib/auth-client";
      
      const { data, error } = await authClient.signUp.email({
        email: "user@example.com",
        password: "securePassword123",
        name: "John Doe",
        image: "https://example.com/avatar.jpg", // optional
        callbackURL: "/dashboard" // optional
      }, {
        onSuccess: (ctx) => {
          // ctx.data contains user and session
          console.log("User created:", ctx.data.user);
        },
        onError: (ctx) => {
          alert(ctx.error.message);
        }
      });
      ```
      
      ### Sign In
      
      ```ts
      const { data, error } = await authClient.signIn.email({
        email: "user@example.com",
        password: "securePassword123",
        callbackURL: "/dashboard",
        rememberMe: true // default: true
      }, {
        onSuccess: () => {
          // redirect or update UI
        },
        onError: (ctx) => {
          console.error(ctx.error.message);
        }
      });
      ```
      
      ### Sign Out
      
      ```ts
      await authClient.signOut({
        fetchOptions: {
          onSuccess: () => {
            router.push("/login");
          }
        }
      });
      ```
      
      ## Email Verification
      
      ### Server Setup
      
      ```ts
      export const auth = betterAuth({
        emailVerification: {
          sendVerificationEmail: async ({ user, url, token }) => {
            // Send verification email
            await sendEmail({
              to: user.email,
              subject: "Verify your email",
              html: `Click <a href="${url}">here</a> to verify your email.`
            });
          },
          sendOnSignUp: true, // Send verification email on signup
          autoSignInAfterVerification: true // Auto sign-in after verification
        },
        emailAndPassword: {
          enabled: true,
          requireEmailVerification: true // Require verification before login
        }
      });
      ```
      
      ### Client Usage
      
      ```ts
      // Send verification email
      await authClient.sendVerificationEmail({
        email: "user@example.com",
        callbackURL: "/verify-success"
      });
      
      // Verify email with token
      await authClient.verifyEmail({
        token: "verification-token-from-email"
      });
      ```
      
      ## Password Reset Flow
      
      ### Server Setup
      
      ```ts
      export const auth = betterAuth({
        emailAndPassword: {
          enabled: true,
          sendResetPasswordToken: async ({ user, url, token }) => {
            await sendEmail({
              to: user.email,
              subject: "Reset your password",
              html: `Click <a href="${url}">here</a> to reset your password.`
            });
          }
        }
      });
      ```
      
      ### Client Flow
      
      ```ts
      // Step 1: Request password reset
      await authClient.forgetPassword({
        email: "user@example.com",
        redirectTo: "/reset-password"
      });
      
      // Step 2: Reset password with token
      await authClient.resetPassword({
        token: "reset-token-from-email",
        password: "newSecurePassword123"
      });
      ```
      
      ### Change Password (Authenticated)
      
      ```ts
      await authClient.changePassword({
        currentPassword: "oldPassword123",
        newPassword: "newPassword456",
        revokeOtherSessions: true // Optional: logout other sessions
      });
      ```
      
      ## Username Authentication
      
      Requires `username` plugin for username-based auth.
      
      ### Server Setup
      
      ```ts
      import { betterAuth } from "better-auth";
      import { username } from "better-auth/plugins";
      
      export const auth = betterAuth({
        plugins: [
          username({
            // Allow sign in with username or email
            allowUsernameOrEmail: true
          })
        ]
      });
      ```
      
      ### Client Setup
      
      ```ts
      import { createAuthClient } from "better-auth/client";
      import { usernameClient } from "better-auth/client/plugins";
      
      export const authClient = createAuthClient({
        plugins: [usernameClient()]
      });
      ```
      
      ### Client Usage
      
      ```ts
      // Sign up with username
      await authClient.signUp.username({
        username: "johndoe",
        password: "securePassword123",
        email: "john@example.com", // optional
        name: "John Doe"
      });
      
      // Sign in with username
      await authClient.signIn.username({
        username: "johndoe",
        password: "securePassword123"
      });
      
      // Sign in with username or email (if allowUsernameOrEmail: true)
      await authClient.signIn.username({
        username: "johndoe", // or "john@example.com"
        password: "securePassword123"
      });
      ```
      
      ## Framework Setup
      
      ### Next.js (App Router)
      
      ```ts
      // app/api/auth/[...all]/route.ts
      import { auth } from "@/lib/auth";
      import { toNextJsHandler } from "better-auth/next-js";
      
      export const { POST, GET } = toNextJsHandler(auth);
      ```
      
      ### Next.js (Pages Router)
      
      ```ts
      // pages/api/auth/[...all].ts
      import { auth } from "@/lib/auth";
      import { toNextJsHandler } from "better-auth/next-js";
      
      export default toNextJsHandler(auth);
      ```
      
      ### Nuxt
      
      ```ts
      // server/api/auth/[...all].ts
      import { auth } from "~/utils/auth";
      import { toWebRequest } from "better-auth/utils/web";
      
      export default defineEventHandler((event) => {
        return auth.handler(toWebRequest(event));
      });
      ```
      
      ### SvelteKit
      
      ```ts
      // hooks.server.ts
      import { auth } from "$lib/auth";
      import { svelteKitHandler } from "better-auth/svelte-kit";
      
      export async function handle({ event, resolve }) {
        return svelteKitHandler({ event, resolve, auth });
      }
      ```
      
      ### Astro
      
      ```ts
      // pages/api/auth/[...all].ts
      import { auth } from "@/lib/auth";
      
      export async function ALL({ request }: { request: Request }) {
        return auth.handler(request);
      }
      ```
      
      ### Hono
      
      ```ts
      import { Hono } from "hono";
      import { auth } from "./auth";
      
      const app = new Hono();
      
      app.on(["POST", "GET"], "/api/auth/*", (c) => {
        return auth.handler(c.req.raw);
      });
      ```
      
      ### Express
      
      ```ts
      import express from "express";
      import { toNodeHandler } from "better-auth/node";
      import { auth } from "./auth";
      
      const app = express();
      
      app.all("/api/auth/*", toNodeHandler(auth));
      ```
      
      ## Protected Routes
      
      ### Next.js Middleware
      
      ```ts
      // middleware.ts
      import { auth } from "@/lib/auth";
      import { NextRequest, NextResponse } from "next/server";
      
      export async function middleware(request: NextRequest) {
        const session = await auth.api.getSession({
          headers: request.headers
        });
      
        if (!session) {
          return NextResponse.redirect(new URL("/login", request.url));
        }
      
        return NextResponse.next();
      }
      
      export const config = {
        matcher: ["/dashboard/:path*", "/profile/:path*"]
      };
      ```
      
      ### SvelteKit Hooks
      
      ```ts
      // hooks.server.ts
      import { auth } from "$lib/auth";
      import { redirect } from "@sveltejs/kit";
      
      export async function handle({ event, resolve }) {
        const session = await auth.api.getSession({
          headers: event.request.headers
        });
      
        if (event.url.pathname.startsWith("/dashboard") && !session) {
          throw redirect(303, "/login");
        }
      
        return resolve(event);
      }
      ```
      
      ### Nuxt Middleware
      
      ```ts
      // middleware/auth.ts
      export default defineNuxtRouteMiddleware(async (to) => {
        const { data: session } = await useAuthSession();
      
        if (!session.value && to.path.startsWith("/dashboard")) {
          return navigateTo("/login");
        }
      });
      ```
      
      ## User Profile Management
      
      ### Get Current User
      
      ```ts
      const { data: session } = await authClient.getSession();
      console.log(session.user);
      ```
      
      ### Update User Profile
      
      ```ts
      await authClient.updateUser({
        name: "New Name",
        image: "https://example.com/new-avatar.jpg",
        // Custom fields if defined in schema
      });
      ```
      
      ### Delete User Account
      
      ```ts
      await authClient.deleteUser({
        password: "currentPassword", // Required for security
        callbackURL: "/" // Redirect after deletion
      });
      ```
      
      ## Best Practices
      
      1. **Password Security**: Enforce strong password requirements
      2. **Email Verification**: Enable for production to prevent spam
      3. **Rate Limiting**: Prevent brute force attacks (see advanced-features.md)
      4. **HTTPS**: Always use HTTPS in production
      5. **Error Messages**: Don't reveal if email exists during login
      6. **Session Security**: Use secure, httpOnly cookies
      7. **CSRF Protection**: Better Auth handles this automatically
      8. **Password Reset**: Set short expiration for reset tokens
      9. **Account Lockout**: Consider implementing after N failed attempts
      10. **Audit Logs**: Track auth events for security monitoring
      
    • oauth-providers.md 9.1 KB
      # OAuth Providers
      
      Better Auth provides built-in OAuth 2.0 support for social authentication. No plugins required.
      
      ## Supported Providers
      
      GitHub, Google, Apple, Discord, Facebook, Microsoft, Twitter/X, Spotify, Twitch, LinkedIn, Dropbox, GitLab, and more.
      
      ## Basic OAuth Setup
      
      ### Server Configuration
      
      ```ts
      import { betterAuth } from "better-auth";
      
      export const auth = betterAuth({
        socialProviders: {
          github: {
            clientId: process.env.GITHUB_CLIENT_ID!,
            clientSecret: process.env.GITHUB_CLIENT_SECRET!,
            // Optional: custom scopes
            scope: ["user:email", "read:user"]
          },
          google: {
            clientId: process.env.GOOGLE_CLIENT_ID!,
            clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
            scope: ["openid", "email", "profile"]
          },
          discord: {
            clientId: process.env.DISCORD_CLIENT_ID!,
            clientSecret: process.env.DISCORD_CLIENT_SECRET!,
          }
        }
      });
      ```
      
      ### Client Usage
      
      ```ts
      import { authClient } from "@/lib/auth-client";
      
      // Basic sign in
      await authClient.signIn.social({
        provider: "github",
        callbackURL: "/dashboard"
      });
      
      // With callbacks
      await authClient.signIn.social({
        provider: "google",
        callbackURL: "/dashboard",
        errorCallbackURL: "/error",
        newUserCallbackURL: "/welcome", // For first-time users
      });
      ```
      
      ## Provider Configuration
      
      ### GitHub OAuth
      
      1. Create OAuth App at https://github.com/settings/developers
      2. Set Authorization callback URL: `http://localhost:3000/api/auth/callback/github`
      3. Add credentials to `.env`:
      
      ```env
      GITHUB_CLIENT_ID=your_client_id
      GITHUB_CLIENT_SECRET=your_client_secret
      ```
      
      ### Google OAuth
      
      1. Create project at https://console.cloud.google.com
      2. Enable Google+ API
      3. Create OAuth 2.0 credentials
      4. Add authorized redirect URI: `http://localhost:3000/api/auth/callback/google`
      5. Add credentials to `.env`:
      
      ```env
      GOOGLE_CLIENT_ID=your_client_id.apps.googleusercontent.com
      GOOGLE_CLIENT_SECRET=your_client_secret
      ```
      
      ### Discord OAuth
      
      1. Create application at https://discord.com/developers/applications
      2. Add OAuth2 redirect: `http://localhost:3000/api/auth/callback/discord`
      3. Add credentials:
      
      ```env
      DISCORD_CLIENT_ID=your_client_id
      DISCORD_CLIENT_SECRET=your_client_secret
      ```
      
      ### Apple Sign In
      
      ```ts
      export const auth = betterAuth({
        socialProviders: {
          apple: {
            clientId: process.env.APPLE_CLIENT_ID!,
            clientSecret: process.env.APPLE_CLIENT_SECRET!,
            teamId: process.env.APPLE_TEAM_ID!,
            keyId: process.env.APPLE_KEY_ID!,
            privateKey: process.env.APPLE_PRIVATE_KEY!
          }
        }
      });
      ```
      
      ### Microsoft/Azure AD
      
      ```ts
      export const auth = betterAuth({
        socialProviders: {
          microsoft: {
            clientId: process.env.MICROSOFT_CLIENT_ID!,
            clientSecret: process.env.MICROSOFT_CLIENT_SECRET!,
            tenantId: process.env.MICROSOFT_TENANT_ID, // Optional: for specific tenant
          }
        }
      });
      ```
      
      ### Twitter/X OAuth
      
      ```ts
      export const auth = betterAuth({
        socialProviders: {
          twitter: {
            clientId: process.env.TWITTER_CLIENT_ID!,
            clientSecret: process.env.TWITTER_CLIENT_SECRET!,
          }
        }
      });
      ```
      
      ## Custom OAuth Provider
      
      Add custom OAuth 2.0 provider:
      
      ```ts
      import { betterAuth } from "better-auth";
      
      export const auth = betterAuth({
        socialProviders: {
          customProvider: {
            clientId: process.env.CUSTOM_CLIENT_ID!,
            clientSecret: process.env.CUSTOM_CLIENT_SECRET!,
            authorizationUrl: "https://provider.com/oauth/authorize",
            tokenUrl: "https://provider.com/oauth/token",
            userInfoUrl: "https://provider.com/oauth/userinfo",
            scope: ["email", "profile"],
            // Map provider user data to Better Auth user
            mapProfile: (profile) => ({
              id: profile.id,
              email: profile.email,
              name: profile.name,
              image: profile.avatar_url
            })
          }
        }
      });
      ```
      
      ## Account Linking
      
      Link multiple OAuth providers to same user account.
      
      ### Server Setup
      
      ```ts
      export const auth = betterAuth({
        account: {
          accountLinking: {
            enabled: true,
            trustedProviders: ["google", "github"] // Auto-link these providers
          }
        }
      });
      ```
      
      ### Client Usage
      
      ```ts
      // Link new provider to existing account
      await authClient.linkSocial({
        provider: "google",
        callbackURL: "/profile"
      });
      
      // List linked accounts
      const { data: session } = await authClient.getSession();
      const accounts = session.user.accounts;
      
      // Unlink account
      await authClient.unlinkAccount({
        accountId: "account-id"
      });
      ```
      
      ## Token Management
      
      ### Access OAuth Tokens
      
      ```ts
      // Server-side
      const session = await auth.api.getSession({
        headers: request.headers
      });
      
      const accounts = await auth.api.listAccounts({
        userId: session.user.id
      });
      
      // Get specific provider token
      const githubAccount = accounts.find(a => a.providerId === "github");
      const accessToken = githubAccount.accessToken;
      const refreshToken = githubAccount.refreshToken;
      ```
      
      ### Refresh Tokens
      
      ```ts
      // Manually refresh OAuth token
      const newToken = await auth.api.refreshToken({
        accountId: "account-id"
      });
      ```
      
      ### Use Provider API
      
      ```ts
      // Example: Use GitHub token to fetch repos
      const githubAccount = accounts.find(a => a.providerId === "github");
      
      const response = await fetch("https://api.github.com/user/repos", {
        headers: {
          Authorization: `Bearer ${githubAccount.accessToken}`
        }
      });
      
      const repos = await response.json();
      ```
      
      ## Advanced OAuth Configuration
      
      ### Custom Scopes
      
      ```ts
      export const auth = betterAuth({
        socialProviders: {
          github: {
            clientId: process.env.GITHUB_CLIENT_ID!,
            clientSecret: process.env.GITHUB_CLIENT_SECRET!,
            scope: [
              "user:email",
              "read:user",
              "repo", // Access repositories
              "gist" // Access gists
            ]
          }
        }
      });
      ```
      
      ### State Parameter
      
      Better Auth automatically handles OAuth state parameter for CSRF protection.
      
      ```ts
      // Custom state validation
      export const auth = betterAuth({
        advanced: {
          generateState: async () => {
            // Custom state generation
            return crypto.randomUUID();
          },
          validateState: async (state: string) => {
            // Custom state validation
            return true;
          }
        }
      });
      ```
      
      ### PKCE Support
      
      Better Auth automatically uses PKCE (Proof Key for Code Exchange) for supported providers.
      
      ```ts
      export const auth = betterAuth({
        socialProviders: {
          customProvider: {
            pkce: true, // Enable PKCE
            // ... other config
          }
        }
      });
      ```
      
      ## Error Handling
      
      ### Client-Side
      
      ```ts
      await authClient.signIn.social({
        provider: "github",
        errorCallbackURL: "/auth/error"
      }, {
        onError: (ctx) => {
          console.error("OAuth error:", ctx.error);
          // Handle specific errors
          if (ctx.error.code === "OAUTH_ACCOUNT_ALREADY_LINKED") {
            alert("This account is already linked to another user");
          }
        }
      });
      ```
      
      ### Server-Side
      
      ```ts
      export const auth = betterAuth({
        callbacks: {
          async onOAuthError({ error, provider }) {
            console.error(`OAuth error with ${provider}:`, error);
            // Log to monitoring service
            await logError(error);
          }
        }
      });
      ```
      
      ## Callback URLs
      
      ### Development
      
      ```
      http://localhost:3000/api/auth/callback/{provider}
      ```
      
      ### Production
      
      ```
      https://yourdomain.com/api/auth/callback/{provider}
      ```
      
      **Important:** Add all callback URLs to OAuth provider settings.
      
      ## UI Components
      
      ### Sign In Button (React)
      
      ```tsx
      import { authClient } from "@/lib/auth-client";
      
      export function SocialSignIn() {
        const handleOAuth = async (provider: string) => {
          await authClient.signIn.social({
            provider,
            callbackURL: "/dashboard"
          });
        };
      
        return (
          <div className="space-y-2">
            <button onClick={() => handleOAuth("github")}>
              Sign in with GitHub
            </button>
            <button onClick={() => handleOAuth("google")}>
              Sign in with Google
            </button>
            <button onClick={() => handleOAuth("discord")}>
              Sign in with Discord
            </button>
          </div>
        );
      }
      ```
      
      ## Best Practices
      
      1. **Callback URLs**: Add all environments (dev, staging, prod) to OAuth app
      2. **Scopes**: Request minimum scopes needed
      3. **Token Storage**: Better Auth stores tokens securely in database
      4. **Token Refresh**: Implement automatic token refresh for long-lived sessions
      5. **Account Linking**: Enable for better UX when user signs in with different providers
      6. **Error Handling**: Provide clear error messages for OAuth failures
      7. **Provider Icons**: Use official brand assets for OAuth buttons
      8. **Mobile Deep Links**: Configure deep links for mobile OAuth flows
      9. **Email Matching**: Consider auto-linking accounts with same email
      10. **Privacy**: Inform users what data you access from OAuth providers
      
      ## Common Issues
      
      ### Redirect URI Mismatch
      
      Ensure callback URL in OAuth app matches exactly:
      ```
      http://localhost:3000/api/auth/callback/github
      ```
      
      ### Missing Scopes
      
      Add required scopes for email access:
      ```ts
      scope: ["user:email"] // GitHub
      scope: ["email"] // Google
      ```
      
      ### HTTPS Required
      
      Some providers (Apple, Microsoft) require HTTPS callbacks. Use ngrok for local development:
      ```bash
      ngrok http 3000
      ```
      
      ### CORS Errors
      
      Configure CORS if frontend/backend on different domains:
      ```ts
      export const auth = betterAuth({
        advanced: {
          corsOptions: {
            origin: ["https://yourdomain.com"],
            credentials: true
          }
        }
      });
      ```
      
  • scripts
    • tests
      • test_better_auth_init.py 15.1 KB
        """
        Tests for better_auth_init.py
        
        Covers main functionality with mocked I/O and file operations.
        Target: >80% coverage
        """
        
        import sys
        import pytest
        from pathlib import Path
        from unittest.mock import Mock, patch, mock_open, MagicMock
        from io import StringIO
        
        # Add parent directory to path
        sys.path.insert(0, str(Path(__file__).parent.parent))
        
        from better_auth_init import BetterAuthInit, EnvConfig, main
        
        
        @pytest.fixture
        def mock_project_root(tmp_path):
            """Create mock project root with package.json."""
            (tmp_path / "package.json").write_text("{}")
            return tmp_path
        
        
        @pytest.fixture
        def auth_init(mock_project_root):
            """Create BetterAuthInit instance with mock project root."""
            return BetterAuthInit(project_root=mock_project_root)
        
        
        class TestBetterAuthInit:
            """Test BetterAuthInit class."""
        
            def test_init_with_project_root(self, mock_project_root):
                """Test initialization with explicit project root."""
                init = BetterAuthInit(project_root=mock_project_root)
                assert init.project_root == mock_project_root
                assert init.env_config is None
        
            def test_find_project_root_success(self, mock_project_root, monkeypatch):
                """Test finding project root successfully."""
                monkeypatch.chdir(mock_project_root)
                init = BetterAuthInit()
                assert init.project_root == mock_project_root
        
            def test_find_project_root_failure(self, tmp_path, monkeypatch):
                """Test failure to find project root."""
                # Create path without package.json
                no_package_dir = tmp_path / "no-package"
                no_package_dir.mkdir()
                monkeypatch.chdir(no_package_dir)
        
                # Mock parent to stop infinite loop
                with patch.object(Path, "parent", new_callable=lambda: property(lambda self: self)):
                    with pytest.raises(RuntimeError, match="Could not find project root"):
                        BetterAuthInit()
        
            def test_generate_secret(self):
                """Test secret generation."""
                secret = BetterAuthInit.generate_secret()
                assert len(secret) == 64  # 32 bytes = 64 hex chars
                assert all(c in "0123456789abcdef" for c in secret)
        
                # Test custom length
                secret = BetterAuthInit.generate_secret(length=16)
                assert len(secret) == 32  # 16 bytes = 32 hex chars
        
            def test_parse_env_file(self, tmp_path):
                """Test parsing .env file."""
                env_content = """
        # Comment
        KEY1=value1
        KEY2="value2"
        KEY3='value3'
        INVALID LINE
        KEY4=value=with=equals
        """
                env_file = tmp_path / ".env"
                env_file.write_text(env_content)
        
                result = BetterAuthInit._parse_env_file(env_file)
        
                assert result["KEY1"] == "value1"
                assert result["KEY2"] == "value2"
                assert result["KEY3"] == "value3"
                assert result["KEY4"] == "value=with=equals"
                assert "INVALID" not in result
        
            def test_parse_env_file_missing(self, tmp_path):
                """Test parsing missing .env file."""
                result = BetterAuthInit._parse_env_file(tmp_path / "nonexistent.env")
                assert result == {}
        
            def test_load_env_files(self, auth_init, mock_project_root):
                """Test loading environment variables from multiple files."""
                # Create .env files
                claude_env = mock_project_root / ".claude" / ".env"
                claude_env.parent.mkdir(parents=True, exist_ok=True)
                claude_env.write_text("BASE_VAR=base\nOVERRIDE=claude")
        
                skills_env = mock_project_root / ".claude" / "skills" / ".env"
                skills_env.parent.mkdir(parents=True, exist_ok=True)
                skills_env.write_text("OVERRIDE=skills\nSKILLS_VAR=skills")
        
                # Mock process env (highest priority)
                with patch.dict("os.environ", {"OVERRIDE": "process", "PROCESS_VAR": "process"}):
                    result = auth_init._load_env_files()
        
                assert result["BASE_VAR"] == "base"
                assert result["SKILLS_VAR"] == "skills"
                assert result["OVERRIDE"] == "process"  # Process env wins
                assert result["PROCESS_VAR"] == "process"
        
            def test_prompt_direct_db_sqlite(self, auth_init):
                """Test prompting for SQLite database."""
                with patch("builtins.input", side_effect=["3", "./test.db"]):
                    config = auth_init._prompt_direct_db()
        
                assert config["type"] == "sqlite"
                assert "better-sqlite3" in config["import"]
                assert "./test.db" in config["config"]
        
            def test_prompt_direct_db_postgresql(self, auth_init):
                """Test prompting for PostgreSQL database."""
                with patch("builtins.input", side_effect=["1", "postgresql://localhost/test"]):
                    config = auth_init._prompt_direct_db()
        
                assert config["type"] == "postgresql"
                assert "pg" in config["import"]
                assert config["env_var"] == ("DATABASE_URL", "postgresql://localhost/test")
        
            def test_prompt_direct_db_mysql(self, auth_init):
                """Test prompting for MySQL database."""
                with patch("builtins.input", side_effect=["2", "mysql://localhost/test"]):
                    config = auth_init._prompt_direct_db()
        
                assert config["type"] == "mysql"
                assert "mysql2" in config["import"]
                assert config["env_var"][0] == "DATABASE_URL"
        
            def test_prompt_drizzle(self, auth_init):
                """Test prompting for Drizzle ORM."""
                with patch("builtins.input", return_value="1"):
                    config = auth_init._prompt_drizzle()
        
                assert config["type"] == "drizzle"
                assert config["provider"] == "pg"
                assert "drizzleAdapter" in config["import"]
                assert "drizzleAdapter" in config["config"]
        
            def test_prompt_prisma(self, auth_init):
                """Test prompting for Prisma."""
                with patch("builtins.input", return_value="2"):
                    config = auth_init._prompt_prisma()
        
                assert config["type"] == "prisma"
                assert config["provider"] == "mysql"
                assert "prismaAdapter" in config["import"]
                assert "PrismaClient" in config["import"]
        
            def test_prompt_kysely(self, auth_init):
                """Test prompting for Kysely."""
                config = auth_init._prompt_kysely()
        
                assert config["type"] == "kysely"
                assert "kyselyAdapter" in config["import"]
        
            def test_prompt_mongodb(self, auth_init):
                """Test prompting for MongoDB."""
                with patch("builtins.input", side_effect=["mongodb://localhost/test", "mydb"]):
                    config = auth_init._prompt_mongodb()
        
                assert config["type"] == "mongodb"
                assert "mongodbAdapter" in config["import"]
                assert "mydb" in config["config"]
                assert config["env_var"] == ("MONGODB_URI", "mongodb://localhost/test")
        
            def test_prompt_database(self, auth_init):
                """Test database prompting with different choices."""
                # Test valid choice
                with patch("builtins.input", side_effect=["3", "1"]):
                    config = auth_init.prompt_database()
                assert config["type"] == "prisma"
        
                # Test invalid choice (defaults to direct DB)
                with patch("builtins.input", side_effect=["99", "1", "postgresql://localhost/test"]):
                    with patch("builtins.print"):
                        config = auth_init.prompt_database()
                assert config["type"] == "postgresql"
        
            def test_prompt_auth_methods(self, auth_init):
                """Test prompting for authentication methods."""
                with patch("builtins.input", return_value="1 2 3 5 8"):
                    with patch("builtins.print"):
                        methods = auth_init.prompt_auth_methods()
        
                assert methods == ["1", "2", "3", "5", "8"]
        
            def test_prompt_auth_methods_invalid(self, auth_init):
                """Test filtering invalid auth method choices."""
                with patch("builtins.input", return_value="1 99 abc 3"):
                    with patch("builtins.print"):
                        methods = auth_init.prompt_auth_methods()
        
                assert methods == ["1", "3"]
        
            def test_generate_auth_config_basic(self, auth_init):
                """Test generating basic auth config."""
                db_config = {
                    "import": "import Database from 'better-sqlite3';",
                    "config": "database: new Database('./dev.db')"
                }
                auth_methods = ["1"]  # Email/password only
        
                config = auth_init.generate_auth_config(db_config, auth_methods)
        
                assert "import { betterAuth }" in config
                assert "emailAndPassword" in config
                assert "enabled: true" in config
                assert "better-sqlite3" in config
        
            def test_generate_auth_config_with_oauth(self, auth_init):
                """Test generating config with OAuth providers."""
                db_config = {
                    "import": "import { Pool } from 'pg';",
                    "config": "database: new Pool()"
                }
                auth_methods = ["1", "2", "3", "4"]  # Email + GitHub + Google + Discord
        
                config = auth_init.generate_auth_config(db_config, auth_methods)
        
                assert "socialProviders" in config
                assert "github:" in config
                assert "google:" in config
                assert "discord:" in config
                assert "GITHUB_CLIENT_ID" in config
                assert "GOOGLE_CLIENT_ID" in config
                assert "DISCORD_CLIENT_ID" in config
        
            def test_generate_auth_config_with_plugins(self, auth_init):
                """Test generating config with plugins."""
                db_config = {"import": "", "config": "database: db"}
                auth_methods = ["5", "6", "7", "8"]  # 2FA, Passkey, Magic Link, Username
        
                config = auth_init.generate_auth_config(db_config, auth_methods)
        
                assert "plugins:" in config
                assert "twoFactor" in config
                assert "passkey" in config
                assert "magicLink" in config
                assert "username" in config
                assert "from 'better-auth/plugins'" in config
        
            def test_generate_env_file_basic(self, auth_init):
                """Test generating basic .env file."""
                db_config = {"type": "sqlite"}
                auth_methods = ["1"]
        
                env_content = auth_init.generate_env_file(db_config, auth_methods)
        
                assert "BETTER_AUTH_SECRET=" in env_content
                assert "BETTER_AUTH_URL=http://localhost:3000" in env_content
                assert len(env_content.split("\n")) >= 2
        
            def test_generate_env_file_with_database_url(self, auth_init):
                """Test generating .env with database URL."""
                db_config = {
                    "env_var": ("DATABASE_URL", "postgresql://localhost/test")
                }
                auth_methods = []
        
                env_content = auth_init.generate_env_file(db_config, auth_methods)
        
                assert "DATABASE_URL=postgresql://localhost/test" in env_content
        
            def test_generate_env_file_with_oauth(self, auth_init):
                """Test generating .env with OAuth credentials."""
                db_config = {}
                auth_methods = ["2", "3", "4"]  # GitHub, Google, Discord
        
                env_content = auth_init.generate_env_file(db_config, auth_methods)
        
                assert "GITHUB_CLIENT_ID=" in env_content
                assert "GITHUB_CLIENT_SECRET=" in env_content
                assert "GOOGLE_CLIENT_ID=" in env_content
                assert "GOOGLE_CLIENT_SECRET=" in env_content
                assert "DISCORD_CLIENT_ID=" in env_content
                assert "DISCORD_CLIENT_SECRET=" in env_content
        
            def test_save_files(self, auth_init, mock_project_root):
                """Test saving configuration files."""
                auth_config = "// auth config"
                env_content = "SECRET=test"
        
                with patch("builtins.input", side_effect=["1"]):
                    auth_init._save_files(auth_config, env_content)
        
                # Check auth.ts was saved
                auth_path = mock_project_root / "lib" / "auth.ts"
                assert auth_path.exists()
                assert auth_path.read_text() == auth_config
        
                # Check .env was saved
                env_path = mock_project_root / ".env"
                assert env_path.exists()
                assert env_path.read_text() == env_content
        
            def test_save_files_custom_path(self, auth_init, mock_project_root):
                """Test saving with custom path."""
                auth_config = "// config"
                env_content = "SECRET=test"
        
                custom_path = str(mock_project_root / "custom" / "auth.ts")
                with patch("builtins.input", side_effect=["5", custom_path]):
                    auth_init._save_files(auth_config, env_content)
        
                assert Path(custom_path).exists()
        
            def test_save_files_backup_existing_env(self, auth_init, mock_project_root):
                """Test backing up existing .env file."""
                # Create existing .env
                env_path = mock_project_root / ".env"
                env_path.write_text("OLD_SECRET=old")
        
                auth_config = "// config"
                env_content = "NEW_SECRET=new"
        
                with patch("builtins.input", return_value="1"):
                    auth_init._save_files(auth_config, env_content)
        
                # Check backup was created
                backup_path = mock_project_root / ".env.backup"
                assert backup_path.exists()
                assert backup_path.read_text() == "OLD_SECRET=old"
        
                # Check new .env
                assert env_path.read_text() == "NEW_SECRET=new"
        
            def test_run_full_flow(self, auth_init, mock_project_root):
                """Test complete run flow."""
                inputs = [
                    "1",  # Direct DB
                    "1",  # PostgreSQL
                    "postgresql://localhost/test",
                    "1 2",  # Email + GitHub
                    "n"  # Don't save
                ]
        
                with patch("builtins.input", side_effect=inputs):
                    with patch("builtins.print"):
                        auth_init.run()
        
                # Should complete without errors
                # Files not saved because user chose 'n'
                assert not (mock_project_root / "auth.ts").exists()
        
            def test_run_save_files(self, auth_init, mock_project_root):
                """Test run flow with file saving."""
                inputs = [
                    "1",  # Direct DB
                    "3",  # SQLite
                    "",   # Default path
                    "1",  # Email only
                    "y",  # Save
                    "1"   # Save location
                ]
        
                with patch("builtins.input", side_effect=inputs):
                    with patch("builtins.print"):
                        auth_init.run()
        
                # Check files were created
                assert (mock_project_root / "lib" / "auth.ts").exists()
                assert (mock_project_root / ".env").exists()
        
        
        class TestMainFunction:
            """Test main entry point."""
        
            def test_main_success(self, tmp_path, monkeypatch):
                """Test successful main execution."""
                (tmp_path / "package.json").write_text("{}")
                monkeypatch.chdir(tmp_path)
        
                inputs = ["1", "3", "", "1", "n"]
        
                with patch("builtins.input", side_effect=inputs):
                    with patch("builtins.print"):
                        exit_code = main()
        
                assert exit_code == 0
        
            def test_main_keyboard_interrupt(self, tmp_path, monkeypatch):
                """Test main with keyboard interrupt."""
                (tmp_path / "package.json").write_text("{}")
                monkeypatch.chdir(tmp_path)
        
                with patch("builtins.input", side_effect=KeyboardInterrupt()):
                    with patch("builtins.print"):
                        exit_code = main()
        
                assert exit_code == 1
        
            def test_main_error(self, tmp_path, monkeypatch):
                """Test main with error."""
                # No package.json - should fail
                no_package = tmp_path / "no-package"
                no_package.mkdir()
                monkeypatch.chdir(no_package)
        
                with patch.object(Path, "parent", new_callable=lambda: property(lambda self: self)):
                    with patch("sys.stderr", new_callable=StringIO):
                        exit_code = main()
        
                assert exit_code == 1
        
        
        if __name__ == "__main__":
            pytest.main([__file__, "-v", "--cov=better_auth_init", "--cov-report=term-missing"])
        
    • better_auth_init.py 16 KB
      #!/usr/bin/env python3
      """
      Better Auth Initialization Script
      
      Interactive script to initialize Better Auth configuration.
      Supports multiple databases, ORMs, and authentication methods.
      
      .env loading order: process.env > skill/.env > skills/.env > .claude/.env
      """
      
      import os
      import sys
      import json
      import secrets
      from pathlib import Path
      from typing import Optional, Dict, Any, List
      from dataclasses import dataclass
      
      
      @dataclass
      class EnvConfig:
          """Environment configuration holder."""
          secret: str
          url: str
          database_url: Optional[str] = None
          github_client_id: Optional[str] = None
          github_client_secret: Optional[str] = None
          google_client_id: Optional[str] = None
          google_client_secret: Optional[str] = None
      
      
      class BetterAuthInit:
          """Better Auth configuration initializer."""
      
          def __init__(self, project_root: Optional[Path] = None):
              """
              Initialize the Better Auth configuration tool.
      
              Args:
                  project_root: Project root directory. Auto-detected if not provided.
              """
              self.project_root = project_root or self._find_project_root()
              self.env_config: Optional[EnvConfig] = None
      
          @staticmethod
          def _find_project_root() -> Path:
              """
              Find project root by looking for package.json.
      
              Returns:
                  Path to project root.
      
              Raises:
                  RuntimeError: If project root cannot be found.
              """
              current = Path.cwd()
              while current != current.parent:
                  if (current / "package.json").exists():
                      return current
                  current = current.parent
      
              raise RuntimeError("Could not find project root (no package.json found)")
      
          def _load_env_files(self) -> Dict[str, str]:
              """
              Load environment variables from .env files in order.
      
              Loading order: process.env > skill/.env > skills/.env > .claude/.env
      
              Returns:
                  Dictionary of environment variables.
              """
              env_vars = {}
      
              # Define search paths in reverse priority order
              skill_dir = Path(__file__).parent.parent
              env_paths = [
                  self.project_root / ".claude" / ".env",
                  self.project_root / ".claude" / "skills" / ".env",
                  skill_dir / ".env",
              ]
      
              # Load from files (lowest priority first)
              for env_path in env_paths:
                  if env_path.exists():
                      env_vars.update(self._parse_env_file(env_path))
      
              # Override with process environment (highest priority)
              env_vars.update(os.environ)
      
              return env_vars
      
          @staticmethod
          def _parse_env_file(path: Path) -> Dict[str, str]:
              """
              Parse .env file into dictionary.
      
              Args:
                  path: Path to .env file.
      
              Returns:
                  Dictionary of key-value pairs.
              """
              env_vars = {}
              try:
                  with open(path, "r") as f:
                      for line in f:
                          line = line.strip()
                          if line and not line.startswith("#") and "=" in line:
                              key, value = line.split("=", 1)
                              # Remove quotes if present
                              value = value.strip().strip('"').strip("'")
                              env_vars[key.strip()] = value
              except Exception as e:
                  print(f"Warning: Could not parse {path}: {e}")
      
              return env_vars
      
          @staticmethod
          def generate_secret(length: int = 32) -> str:
              """
              Generate cryptographically secure random secret.
      
              Args:
                  length: Length of secret in bytes.
      
              Returns:
                  Hex-encoded secret string.
              """
              return secrets.token_hex(length)
      
          def prompt_database(self) -> Dict[str, Any]:
              """
              Prompt user for database configuration.
      
              Returns:
                  Database configuration dictionary.
              """
              print("\nDatabase Configuration")
              print("=" * 50)
              print("1. Direct Connection (PostgreSQL/MySQL/SQLite)")
              print("2. Drizzle ORM")
              print("3. Prisma")
              print("4. Kysely")
              print("5. MongoDB")
      
              choice = input("\nSelect database option (1-5): ").strip()
      
              db_configs = {
                  "1": self._prompt_direct_db,
                  "2": self._prompt_drizzle,
                  "3": self._prompt_prisma,
                  "4": self._prompt_kysely,
                  "5": self._prompt_mongodb,
              }
      
              handler = db_configs.get(choice)
              if not handler:
                  print("Invalid choice. Defaulting to direct PostgreSQL.")
                  return self._prompt_direct_db()
      
              return handler()
      
          def _prompt_direct_db(self) -> Dict[str, Any]:
              """Prompt for direct database connection."""
              print("\nDatabase Type:")
              print("1. PostgreSQL")
              print("2. MySQL")
              print("3. SQLite")
      
              db_type = input("Select (1-3): ").strip()
      
              if db_type == "3":
                  db_path = input("SQLite file path [./dev.db]: ").strip() or "./dev.db"
                  return {
                      "type": "sqlite",
                      "import": "import Database from 'better-sqlite3';",
                      "config": f'database: new Database("{db_path}")'
                  }
              elif db_type == "2":
                  db_url = input("MySQL connection string: ").strip()
                  return {
                      "type": "mysql",
                      "import": "import { createPool } from 'mysql2/promise';",
                      "config": f"database: createPool({{ connectionString: process.env.DATABASE_URL }})",
                      "env_var": ("DATABASE_URL", db_url)
                  }
              else:
                  db_url = input("PostgreSQL connection string: ").strip()
                  return {
                      "type": "postgresql",
                      "import": "import { Pool } from 'pg';",
                      "config": "database: new Pool({ connectionString: process.env.DATABASE_URL })",
                      "env_var": ("DATABASE_URL", db_url)
                  }
      
          def _prompt_drizzle(self) -> Dict[str, Any]:
              """Prompt for Drizzle ORM configuration."""
              print("\nDrizzle Provider:")
              print("1. PostgreSQL")
              print("2. MySQL")
              print("3. SQLite")
      
              provider = input("Select (1-3): ").strip()
              provider_map = {"1": "pg", "2": "mysql", "3": "sqlite"}
              provider_name = provider_map.get(provider, "pg")
      
              return {
                  "type": "drizzle",
                  "provider": provider_name,
                  "import": "import { drizzleAdapter } from 'better-auth/adapters/drizzle';\nimport { db } from '@/db';",
                  "config": f"database: drizzleAdapter(db, {{ provider: '{provider_name}' }})"
              }
      
          def _prompt_prisma(self) -> Dict[str, Any]:
              """Prompt for Prisma configuration."""
              print("\nPrisma Provider:")
              print("1. PostgreSQL")
              print("2. MySQL")
              print("3. SQLite")
      
              provider = input("Select (1-3): ").strip()
              provider_map = {"1": "postgresql", "2": "mysql", "3": "sqlite"}
              provider_name = provider_map.get(provider, "postgresql")
      
              return {
                  "type": "prisma",
                  "provider": provider_name,
                  "import": "import { prismaAdapter } from 'better-auth/adapters/prisma';\nimport { PrismaClient } from '@prisma/client';\n\nconst prisma = new PrismaClient();",
                  "config": f"database: prismaAdapter(prisma, {{ provider: '{provider_name}' }})"
              }
      
          def _prompt_kysely(self) -> Dict[str, Any]:
              """Prompt for Kysely configuration."""
              return {
                  "type": "kysely",
                  "import": "import { kyselyAdapter } from 'better-auth/adapters/kysely';\nimport { db } from '@/db';",
                  "config": "database: kyselyAdapter(db, { provider: 'pg' })"
              }
      
          def _prompt_mongodb(self) -> Dict[str, Any]:
              """Prompt for MongoDB configuration."""
              mongo_uri = input("MongoDB connection string: ").strip()
              db_name = input("Database name: ").strip()
      
              return {
                  "type": "mongodb",
                  "import": "import { mongodbAdapter } from 'better-auth/adapters/mongodb';\nimport { client } from '@/db';",
                  "config": f"database: mongodbAdapter(client, {{ databaseName: '{db_name}' }})",
                  "env_var": ("MONGODB_URI", mongo_uri)
              }
      
          def prompt_auth_methods(self) -> List[str]:
              """
              Prompt user for authentication methods.
      
              Returns:
                  List of selected auth method codes.
              """
              print("\nAuthentication Methods")
              print("=" * 50)
              print("Select authentication methods (space-separated, e.g., '1 2 3'):")
              print("1. Email/Password")
              print("2. GitHub OAuth")
              print("3. Google OAuth")
              print("4. Discord OAuth")
              print("5. Two-Factor Authentication (2FA)")
              print("6. Passkeys (WebAuthn)")
              print("7. Magic Link")
              print("8. Username")
      
              choices = input("\nYour selection: ").strip().split()
              return [c for c in choices if c in "12345678"]
      
          def generate_auth_config(
              self,
              db_config: Dict[str, Any],
              auth_methods: List[str],
          ) -> str:
              """
              Generate auth.ts configuration file content.
      
              Args:
                  db_config: Database configuration.
                  auth_methods: Selected authentication methods.
      
              Returns:
                  Generated TypeScript configuration code.
              """
              imports = ["import { betterAuth } from 'better-auth';"]
              plugins = []
              plugin_imports = []
              config_parts = []
      
              # Database import
              if db_config.get("import"):
                  imports.append(db_config["import"])
      
              # Email/Password
              if "1" in auth_methods:
                  config_parts.append("""  emailAndPassword: {
          enabled: true,
          autoSignIn: true
        }""")
      
              # OAuth providers
              social_providers = []
              if "2" in auth_methods:
                  social_providers.append("""    github: {
            clientId: process.env.GITHUB_CLIENT_ID!,
            clientSecret: process.env.GITHUB_CLIENT_SECRET!,
          }""")
      
              if "3" in auth_methods:
                  social_providers.append("""    google: {
            clientId: process.env.GOOGLE_CLIENT_ID!,
            clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
          }""")
      
              if "4" in auth_methods:
                  social_providers.append("""    discord: {
            clientId: process.env.DISCORD_CLIENT_ID!,
            clientSecret: process.env.DISCORD_CLIENT_SECRET!,
          }""")
      
              if social_providers:
                  config_parts.append(f"  socialProviders: {{\n{',\\n'.join(social_providers)}\n  }}")
      
              # Plugins
              if "5" in auth_methods:
                  plugin_imports.append("import { twoFactor } from 'better-auth/plugins';")
                  plugins.append("twoFactor()")
      
              if "6" in auth_methods:
                  plugin_imports.append("import { passkey } from 'better-auth/plugins';")
                  plugins.append("passkey()")
      
              if "7" in auth_methods:
                  plugin_imports.append("import { magicLink } from 'better-auth/plugins';")
                  plugins.append("""magicLink({
            sendMagicLink: async ({ email, url }) => {
              // TODO: Implement email sending
              console.log(`Magic link for ${email}: ${url}`);
            }
          })""")
      
              if "8" in auth_methods:
                  plugin_imports.append("import { username } from 'better-auth/plugins';")
                  plugins.append("username()")
      
              # Combine all imports
              all_imports = imports + plugin_imports
      
              # Build config
              config_body = ",\n".join(config_parts)
      
              if plugins:
                  plugins_str = ",\n    ".join(plugins)
                  config_body += f",\n  plugins: [\n    {plugins_str}\n  ]"
      
              # Final output
              return f"""{chr(10).join(all_imports)}
      
      export const auth = betterAuth({{
        {db_config["config"]},
      {config_body}
      }});
      """
      
          def generate_env_file(
              self,
              db_config: Dict[str, Any],
              auth_methods: List[str]
          ) -> str:
              """
              Generate .env file content.
      
              Args:
                  db_config: Database configuration.
                  auth_methods: Selected authentication methods.
      
              Returns:
                  Generated .env file content.
              """
              env_vars = [
                  f"BETTER_AUTH_SECRET={self.generate_secret()}",
                  "BETTER_AUTH_URL=http://localhost:3000",
              ]
      
              # Database URL
              if db_config.get("env_var"):
                  key, value = db_config["env_var"]
                  env_vars.append(f"{key}={value}")
      
              # OAuth credentials
              if "2" in auth_methods:
                  env_vars.extend([
                      "GITHUB_CLIENT_ID=your_github_client_id",
                      "GITHUB_CLIENT_SECRET=your_github_client_secret",
                  ])
      
              if "3" in auth_methods:
                  env_vars.extend([
                      "GOOGLE_CLIENT_ID=your_google_client_id",
                      "GOOGLE_CLIENT_SECRET=your_google_client_secret",
                  ])
      
              if "4" in auth_methods:
                  env_vars.extend([
                      "DISCORD_CLIENT_ID=your_discord_client_id",
                      "DISCORD_CLIENT_SECRET=your_discord_client_secret",
                  ])
      
              return "\n".join(env_vars) + "\n"
      
          def run(self) -> None:
              """Run interactive initialization."""
              print("=" * 50)
              print("Better Auth Configuration Generator")
              print("=" * 50)
      
              # Load existing env
              env_vars = self._load_env_files()
      
              # Prompt for configuration
              db_config = self.prompt_database()
              auth_methods = self.prompt_auth_methods()
      
              # Generate files
              auth_config = self.generate_auth_config(db_config, auth_methods)
              env_content = self.generate_env_file(db_config, auth_methods)
      
              # Display output
              print("\n" + "=" * 50)
              print("Generated Configuration")
              print("=" * 50)
      
              print("\n--- auth.ts ---")
              print(auth_config)
      
              print("\n--- .env ---")
              print(env_content)
      
              # Offer to save
              save = input("\nSave configuration files? (y/N): ").strip().lower()
              if save == "y":
                  self._save_files(auth_config, env_content)
              else:
                  print("Configuration not saved.")
      
          def _save_files(self, auth_config: str, env_content: str) -> None:
              """
              Save generated configuration files.
      
              Args:
                  auth_config: auth.ts content.
                  env_content: .env content.
              """
              # Save auth.ts
              auth_locations = [
                  self.project_root / "lib" / "auth.ts",
                  self.project_root / "src" / "lib" / "auth.ts",
                  self.project_root / "utils" / "auth.ts",
                  self.project_root / "auth.ts",
              ]
      
              print("\nWhere to save auth.ts?")
              for i, loc in enumerate(auth_locations, 1):
                  print(f"{i}. {loc}")
              print("5. Custom path")
      
              choice = input("Select (1-5): ").strip()
              if choice == "5":
                  custom_path = input("Enter path: ").strip()
                  auth_path = Path(custom_path)
              else:
                  idx = int(choice) - 1 if choice.isdigit() else 0
                  auth_path = auth_locations[idx]
      
              auth_path.parent.mkdir(parents=True, exist_ok=True)
              auth_path.write_text(auth_config)
              print(f"Saved: {auth_path}")
      
              # Save .env
              env_path = self.project_root / ".env"
              if env_path.exists():
                  backup = self.project_root / ".env.backup"
                  env_path.rename(backup)
                  print(f"Backed up existing .env to {backup}")
      
              env_path.write_text(env_content)
              print(f"Saved: {env_path}")
      
              print("\nNext steps:")
              print("1. Run: npx @better-auth/cli generate")
              print("2. Apply database migrations")
              print("3. Mount API handler in your framework")
              print("4. Create client instance")
      
      
      def main() -> int:
          """
          Main entry point.
      
          Returns:
              Exit code (0 for success, 1 for error).
          """
          try:
              initializer = BetterAuthInit()
              initializer.run()
              return 0
          except KeyboardInterrupt:
              print("\n\nOperation cancelled.")
              return 1
          except Exception as e:
              print(f"\nError: {e}", file=sys.stderr)
              return 1
      
      
      if __name__ == "__main__":
          sys.exit(main())
      
    • requirements.txt 402 B
      # Better Auth Skill Dependencies
      # Python 3.10+ required
      
      # No Python package dependencies - uses only standard library
      
      # Testing dependencies (dev)
      pytest>=8.0.0
      pytest-cov>=4.1.0
      pytest-mock>=3.12.0
      
      # Note: This script generates Better Auth configuration
      # The actual Better Auth library is installed via npm/pnpm/yarn:
      #   npm install better-auth
      #   pnpm add better-auth
      #   yarn add better-auth
      
  • SKILL.md 7.2 KB
    ---
    name: better-auth
    description: Implement authentication and authorization with Better Auth - a framework-agnostic TypeScript authentication framework. Features include email/password authentication with verification, OAuth providers (Google, GitHub, Discord, etc.), two-factor authentication (TOTP, SMS), passkeys/WebAuthn support, session management, role-based access control (RBAC), rate limiting, and database adapters. Use when adding authentication to applications, implementing OAuth flows, setting up 2FA/MFA, managing user sessions, configuring authorization rules, or building secure authentication systems for web applications.
    license: MIT
    version: 2.0.0
    ---
    
    # Better Auth Skill
    
    Better Auth is comprehensive, framework-agnostic authentication/authorization framework for TypeScript with built-in email/password, social OAuth, and powerful plugin ecosystem for advanced features.
    
    ## 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.)
    
    ## Quick Start
    
    ### Installation
    
    ```bash
    npm install better-auth
    # or pnpm/yarn/bun add better-auth
    ```
    
    ### Environment Setup
    
    Create `.env`:
    ```env
    BETTER_AUTH_SECRET=<generated-secret-32-chars-min>
    BETTER_AUTH_URL=http://localhost:3000
    ```
    
    ### Basic Server Setup
    
    Create `auth.ts` (root, lib/, utils/, or under src/app/server/):
    
    ```ts
    import { betterAuth } from "better-auth";
    
    export const auth = betterAuth({
      database: {
        // See references/database-integration.md
      },
      emailAndPassword: {
        enabled: true,
        autoSignIn: true
      },
      socialProviders: {
        github: {
          clientId: process.env.GITHUB_CLIENT_ID!,
          clientSecret: process.env.GITHUB_CLIENT_SECRET!,
        }
      }
    });
    ```
    
    ### Database Schema
    
    ```bash
    npx @better-auth/cli generate  # Generate schema/migrations
    npx @better-auth/cli migrate   # Apply migrations (Kysely only)
    ```
    
    ### Mount API Handler
    
    **Next.js App Router:**
    ```ts
    // app/api/auth/[...all]/route.ts
    import { auth } from "@/lib/auth";
    import { toNextJsHandler } from "better-auth/next-js";
    
    export const { POST, GET } = toNextJsHandler(auth);
    ```
    
    **Other frameworks:** See references/email-password-auth.md#framework-setup
    
    ### Client Setup
    
    Create `auth-client.ts`:
    
    ```ts
    import { createAuthClient } from "better-auth/client";
    
    export const authClient = createAuthClient({
      baseURL: process.env.NEXT_PUBLIC_BETTER_AUTH_URL || "http://localhost:3000"
    });
    ```
    
    ### Basic Usage
    
    ```ts
    // 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
    const { data: session } = authClient.useSession(); // React/Vue/Svelte
    const { data: session } = await authClient.getSession(); // Vanilla JS
    ```
    
    ## Feature Selection Matrix
    
    | Feature | Plugin Required | Use Case | Reference |
    |---------|----------------|----------|-----------|
    | Email/Password | No (built-in) | Basic auth | [email-password-auth.md](./references/email-password-auth.md) |
    | OAuth (GitHub, Google, etc.) | No (built-in) | Social login | [oauth-providers.md](./references/oauth-providers.md) |
    | Email Verification | No (built-in) | Verify email addresses | [email-password-auth.md](./references/email-password-auth.md#email-verification) |
    | Password Reset | No (built-in) | Forgot password flow | [email-password-auth.md](./references/email-password-auth.md#password-reset) |
    | Two-Factor Auth (2FA/TOTP) | Yes (`twoFactor`) | Enhanced security | [advanced-features.md](./references/advanced-features.md#two-factor-authentication) |
    | Passkeys/WebAuthn | Yes (`passkey`) | Passwordless auth | [advanced-features.md](./references/advanced-features.md#passkeys-webauthn) |
    | Magic Link | Yes (`magicLink`) | Email-based login | [advanced-features.md](./references/advanced-features.md#magic-link) |
    | Username Auth | Yes (`username`) | Username login | [email-password-auth.md](./references/email-password-auth.md#username-authentication) |
    | Organizations/Multi-tenant | Yes (`organization`) | Team/org features | [advanced-features.md](./references/advanced-features.md#organizations) |
    | Rate Limiting | No (built-in) | Prevent abuse | [advanced-features.md](./references/advanced-features.md#rate-limiting) |
    | Session Management | No (built-in) | User sessions | [advanced-features.md](./references/advanced-features.md#session-management) |
    
    ## 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)
    
    ## 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
    
    ## Reference Documentation
    
    ### Core Authentication
    - [Email/Password Authentication](./references/email-password-auth.md) - Email/password setup, verification, password reset, username auth
    - [OAuth Providers](./references/oauth-providers.md) - Social login setup, provider configuration, token management
    - [Database Integration](./references/database-integration.md) - Database adapters, schema setup, migrations
    
    ### Advanced Features
    - [Advanced Features](./references/advanced-features.md) - 2FA/MFA, passkeys, magic links, organizations, rate limiting, session management
    
    ## Scripts
    
    - `scripts/better_auth_init.py` - Initialize Better Auth configuration with interactive setup
    
    ## Resources
    
    - Docs: https://www.better-auth.com/docs
    - GitHub: https://github.com/better-auth/better-auth
    - Plugins: https://www.better-auth.com/docs/plugins
    - Examples: https://www.better-auth.com/docs/examples
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related