home / skills / einverne / dotfiles / better-auth

better-auth skill

/claude/skills/better-auth

This skill helps you implement framework-agnostic authentication with TypeScript by guiding setup, session management, and plugin-enabled features.

npx playbooks add skill einverne/dotfiles --skill better-auth

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
15.5 KB
---
name: better-auth
description: Guide for implementing Better Auth - a framework-agnostic authentication and authorization framework for TypeScript. Use when adding authentication features like email/password, OAuth, 2FA, passkeys, or advanced auth functionality to applications.
license: MIT
version: 1.0.0
---

# Better Auth Skill

Better Auth is a comprehensive, framework-agnostic authentication and authorization framework for TypeScript that provides built-in support for email/password authentication, social sign-on, and a powerful plugin ecosystem for advanced features.

## When to Use This Skill

Use this skill when:
- Implementing authentication in TypeScript/JavaScript applications
- Adding email/password or social OAuth authentication
- Setting up 2FA, passkeys, magic links, or other advanced auth features
- Building multi-tenant applications with organization support
- Implementing session management and user management
- Working with any framework (Next.js, Nuxt, SvelteKit, Remix, Astro, Hono, Express, etc.)

## Core Concepts

### Key Features

- **Framework Agnostic**: Works with any framework (Next.js, Nuxt, Svelte, Remix, Hono, Express, etc.)
- **Built-in Auth Methods**: Email/password and OAuth 2.0 social providers
- **Plugin Ecosystem**: Easy-to-add advanced features (2FA, passkeys, magic link, username, email OTP, organization, etc.)
- **Database Flexibility**: Supports SQLite, PostgreSQL, MySQL, MongoDB, and more
- **ORM Support**: Built-in adapters for Drizzle, Prisma, Kysely, and MongoDB
- **Type Safety**: Full TypeScript support with excellent type inference
- **Session Management**: Built-in session handling for both client and server

### Architecture

Better Auth follows a client-server architecture:
1. **Server Instance** (`better-auth`): Handles auth logic, database operations, and API routes
2. **Client Instance** (`better-auth/client`): Provides hooks and methods for authentication
3. **Plugins**: Extend both server and client functionality

## Installation & Setup

### Step 1: Install Package

```bash
npm install better-auth
# or
pnpm add better-auth
# or
yarn add better-auth
# or
bun add better-auth
```

### Step 2: Environment Variables

Create `.env` file:

```env
BETTER_AUTH_SECRET=<generated-secret-key>
BETTER_AUTH_URL=http://localhost:3000
```

Generate secret: Use openssl or a random string generator (min 32 characters).

### Step 3: Create Auth Server Instance

Create `auth.ts` in project root, `lib/`, `utils/`, or nested under `src/`, `app/`, or `server/`:

```ts
import { betterAuth } from "better-auth";

export const auth = betterAuth({
  database: {
    // Database configuration
  },
  emailAndPassword: {
    enabled: true,
    autoSignIn: true // Users auto sign-in after signup
  },
  socialProviders: {
    github: {
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    },
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }
  }
});
```

### Step 4: Database Configuration

Choose your database setup:

**Direct Database Connection:**

```ts
import { betterAuth } from "better-auth";
import Database from "better-sqlite3";
// or import { Pool } from "pg";
// or import { createPool } from "mysql2/promise";

export const auth = betterAuth({
  database: new Database("./sqlite.db"),
  // or: new Pool({ connectionString: process.env.DATABASE_URL })
  // or: createPool({ host: "localhost", user: "root", ... })
});
```

**ORM Adapter:**

```ts
// Drizzle
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "@/db";

export const auth = betterAuth({
  database: drizzleAdapter(db, {
    provider: "pg", // or "mysql", "sqlite"
  }),
});

// Prisma
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",
  }),
});

// MongoDB
import { mongodbAdapter } from "better-auth/adapters/mongodb";
import { client } from "@/db";

export const auth = betterAuth({
  database: mongodbAdapter(client),
});
```

### Step 5: Create Database Schema

Use Better Auth CLI:

```bash
# Generate schema/migration files
npx @better-auth/cli generate

# Or migrate directly (Kysely adapter only)
npx @better-auth/cli migrate
```

### Step 6: Mount API Handler

Create catch-all route for `/api/auth/*`:

**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);
```

**Nuxt:**
```ts
// server/api/auth/[...all].ts
import { auth } from "~/utils/auth";

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 });
}
```

**Hono:**
```ts
import { Hono } from "hono";
import { auth } from "./auth";

const app = new Hono();
app.on(["POST", "GET"], "/api/auth/*", (c) => 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));
```

### Step 7: Create Client Instance

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"
});
```

## Authentication Methods

### Email & Password

**Server Configuration:**
```ts
export const auth = betterAuth({
  emailAndPassword: {
    enabled: true,
    autoSignIn: true, // default: true
  }
});
```

**Client Usage:**

```ts
// Sign Up
const { data, error } = await authClient.signUp.email({
  email: "[email protected]",
  password: "securePassword123",
  name: "John Doe",
  image: "https://example.com/avatar.jpg", // optional
  callbackURL: "/dashboard" // optional
}, {
  onSuccess: (ctx) => {
    // redirect or show success
  },
  onError: (ctx) => {
    alert(ctx.error.message);
  }
});

// Sign In
const { data, error } = await authClient.signIn.email({
  email: "[email protected]",
  password: "securePassword123",
  callbackURL: "/dashboard",
  rememberMe: true // default: true
});
```

### Social OAuth

**Server Configuration:**
```ts
export const auth = betterAuth({
  socialProviders: {
    github: {
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    },
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    },
    // Other providers: apple, discord, facebook, etc.
  }
});
```

**Client Usage:**
```ts
await authClient.signIn.social({
  provider: "github",
  callbackURL: "/dashboard",
  errorCallbackURL: "/error",
  newUserCallbackURL: "/welcome",
});
```

### Sign Out

```ts
await authClient.signOut({
  fetchOptions: {
    onSuccess: () => {
      router.push("/login");
    }
  }
});
```

## Session Management

### Client-Side Session

**Using Hooks (React/Vue/Svelte/Solid):**

```tsx
// React
import { authClient } from "@/lib/auth-client";

export function UserProfile() {
  const { data: session, isPending, error } = authClient.useSession();

  if (isPending) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return <div>Welcome, {session?.user.name}!</div>;
}

// Vue
<script setup>
import { authClient } from "~/lib/auth-client";
const session = authClient.useSession();
</script>

<template>
  <div v-if="session.data">{{ session.data.user.email }}</div>
</template>

// Svelte
<script>
import { authClient } from "$lib/auth-client";
const session = authClient.useSession();
</script>

<p>{$session.data?.user.email}</p>
```

**Using getSession:**
```ts
const { data: session, error } = await authClient.getSession();
```

### Server-Side Session

```ts
// Next.js
import { auth } from "./auth";
import { headers } from "next/headers";

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

// Hono
app.get("/protected", async (c) => {
  const session = await auth.api.getSession({
    headers: c.req.raw.headers
  });

  if (!session) {
    return c.json({ error: "Unauthorized" }, 401);
  }

  return c.json({ user: session.user });
});
```

## Plugin System

Better Auth's plugin system allows adding advanced features easily.

### Using Plugins

**Server-Side:**
```ts
import { betterAuth } from "better-auth";
import { twoFactor, organization, username } from "better-auth/plugins";

export const auth = betterAuth({
  plugins: [
    twoFactor(),
    organization(),
    username(),
  ]
});
```

**Client-Side:**
```ts
import { createAuthClient } from "better-auth/client";
import {
  twoFactorClient,
  organizationClient,
  usernameClient
} from "better-auth/client/plugins";

export const authClient = createAuthClient({
  plugins: [
    twoFactorClient({
      twoFactorPage: "/two-factor"
    }),
    organizationClient(),
    usernameClient()
  ]
});
```

**After Adding Plugins:**
```bash
# Regenerate schema
npx @better-auth/cli generate

# Apply migration
npx @better-auth/cli migrate
```

### Popular Plugins

#### Two-Factor Authentication (2FA)

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

export const auth = betterAuth({
  plugins: [twoFactor()]
});

// Client
import { twoFactorClient } from "better-auth/client/plugins";

export const authClient = createAuthClient({
  plugins: [
    twoFactorClient({ twoFactorPage: "/two-factor" })
  ]
});

// Usage
await authClient.twoFactor.enable({ password: "userPassword" });
await authClient.twoFactor.verifyTOTP({
  code: "123456",
  trustDevice: true
});
```

#### Username Authentication

```ts
// Server
import { username } from "better-auth/plugins";

export const auth = betterAuth({
  plugins: [username()]
});

// Client
import { usernameClient } from "better-auth/client/plugins";

// Sign up with username
await authClient.signUp.username({
  username: "johndoe",
  password: "securePassword123",
  name: "John Doe"
});
```

#### Magic Link

```ts
import { magicLink } from "better-auth/plugins";

export const auth = betterAuth({
  plugins: [
    magicLink({
      sendMagicLink: async ({ email, url }) => {
        // Send email with magic link
        await sendEmail(email, url);
      }
    })
  ]
});
```

#### Passkey (WebAuthn)

```ts
import { passkey } from "better-auth/plugins";

export const auth = betterAuth({
  plugins: [passkey()]
});

// Client
await authClient.passkey.register();
await authClient.passkey.signIn();
```

#### Organization/Multi-Tenancy

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

export const auth = betterAuth({
  plugins: [organization()]
});

// Client
await authClient.organization.create({
  name: "Acme Corp",
  slug: "acme"
});

await authClient.organization.inviteMember({
  organizationId: "org-id",
  email: "[email protected]",
  role: "member"
});
```

## Advanced Configuration

### Email Verification

```ts
export const auth = betterAuth({
  emailVerification: {
    sendVerificationEmail: async ({ user, url }) => {
      await sendEmail(user.email, url);
    },
    sendOnSignUp: true
  }
});
```

### Rate Limiting

```ts
export const auth = betterAuth({
  rateLimit: {
    enabled: true,
    window: 60, // seconds
    max: 10 // requests
  }
});
```

### Custom Session Expiration

```ts
export const auth = betterAuth({
  session: {
    expiresIn: 60 * 60 * 24 * 7, // 7 days in seconds
    updateAge: 60 * 60 * 24 // Update every 24 hours
  }
});
```

### CORS Configuration

```ts
export const auth = betterAuth({
  advanced: {
    corsOptions: {
      origin: ["https://example.com"],
      credentials: true
    }
  }
});
```

## Database Schema

### Core Tables

Better Auth requires these core tables:
- `user`: User accounts
- `session`: Active sessions
- `account`: OAuth provider connections
- `verification`: Email verification tokens

**Auto-generate with CLI:**
```bash
npx @better-auth/cli generate
```

**Manual schema available in docs:** Check `/docs/concepts/database#core-schema`

## Best Practices

1. **Environment Variables**: Always use environment variables for secrets
2. **HTTPS in Production**: Set `BETTER_AUTH_URL` to HTTPS URL
3. **Session Security**: Use secure cookies in production
4. **Error Handling**: Implement proper error handling on client and server
5. **Type Safety**: Leverage TypeScript types for better DX
6. **Plugin Order**: Some plugins depend on others, check documentation
7. **Database Migrations**: Always run migrations after adding plugins
8. **Rate Limiting**: Enable rate limiting for production
9. **Email Verification**: Implement email verification for security
10. **Password Requirements**: Customize password validation as needed

## Common Patterns

### Protected Routes (Server-Side)

```ts
// Next.js middleware
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*"]
};
```

### User Profile Updates

```ts
await authClient.updateUser({
  name: "New Name",
  image: "https://example.com/new-avatar.jpg"
});
```

### Password Management

```ts
// Change password
await authClient.changePassword({
  currentPassword: "oldPassword",
  newPassword: "newPassword"
});

// Reset password (forgot password)
await authClient.forgetPassword({
  email: "[email protected]",
  redirectTo: "/reset-password"
});

await authClient.resetPassword({
  token: "reset-token",
  password: "newPassword"
});
```

## Troubleshooting

### Common Issues

1. **"Unable to find auth instance"**
   - Ensure `auth.ts` is in correct location (root, lib/, utils/)
   - Export auth instance as `auth` or default export

2. **Database connection errors**
   - Verify database credentials
   - Check if database server is running
   - Ensure correct adapter for your database

3. **CORS errors**
   - Configure `corsOptions` in advanced settings
   - Ensure client and server URLs match

4. **Plugin not working**
   - Run migrations after adding plugins
   - Check plugin is added to both server and client
   - Verify plugin configuration

## Framework-Specific Guides

- **Next.js**: Use Next.js plugin for server actions
- **Nuxt**: Configure server middleware
- **SvelteKit**: Use hooks.server.ts
- **Astro**: Set up API routes properly
- **Hono/Express**: Use appropriate node handlers

## Resources

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

## Implementation Checklist

When implementing Better Auth:

- [ ] Install `better-auth` package
- [ ] Set up environment variables (SECRET, URL)
- [ ] Create auth server instance
- [ ] Configure database/adapter
- [ ] Run schema migration
- [ ] Configure authentication methods
- [ ] Mount API handler
- [ ] Create client instance
- [ ] Implement sign-up/sign-in UI
- [ ] Add session management
- [ ] Set up protected routes
- [ ] Add plugins as needed
- [ ] Test authentication flow
- [ ] Configure email sending (if needed)
- [ ] Set up error handling
- [ ] Enable rate limiting for production

Overview

This skill describes Better Auth, a framework-agnostic authentication and authorization framework for TypeScript. It shows how to add email/password, social OAuth, 2FA, passkeys, magic links, and multi-tenant features with built-in session and plugin support. The guide focuses on practical installation, configuration, and common integration patterns across frameworks.

How this skill works

Better Auth exposes a server instance that handles auth logic, database operations, API routes, and a client instance with hooks and methods for sign-in, session handling, and plugin features. It integrates with direct database drivers or ORM adapters (Drizzle, Prisma, Kysely, MongoDB) and uses a plugin system to add advanced features like 2FA, passkeys, username auth, magic links, and organization/multi-tenancy. Routes are mounted per-framework with small adapter helpers for Next.js, SvelteKit, Nuxt, Hono, Express, and others.

When to use it

  • Adding authentication to TypeScript/JavaScript apps regardless of framework
  • Implementing email/password, social OAuth, magic links, passkeys, or 2FA
  • Building multi-tenant or organization-backed auth flows
  • Needing type-safe session management and user APIs
  • Replacing or standardizing auth across frontend and backend stacks

Best practices

  • Store secrets in environment variables and use HTTPS in production
  • Use secure cookies and tune session expiration for your app
  • Run migrations after adding plugins or changing schema
  • Enable rate limiting and email verification in production
  • Leverage TypeScript types and ORM adapters for predictable data models

Example use cases

  • Set up email/password sign-up and auto sign-in with post-signup redirect
  • Add GitHub and Google OAuth for social sign-on and handle new-user flows
  • Enable two-factor authentication with TOTP and trusted devices via plugin
  • Implement passkey (WebAuthn) registration and sign-in for passwordless auth
  • Create organization CRUD and invitation flows for multi-tenant apps

FAQ

Which databases and ORMs are supported?

Better Auth supports direct drivers (SQLite, Postgres, MySQL, MongoDB) and adapters for Drizzle, Prisma, Kysely, and MongoDB clients.

How do I mount auth routes in my framework?

Use provided handlers or adapters (toNextJsHandler, svelteKitHandler, toNodeHandler, Hono integration) to map /api/auth/* to the Better Auth server instance.