jackspace/claudeskillz/better-auth

Trust tier 1
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Production-ready authentication framework for TypeScript with first-class Cloudflare D1 support. Use this skill when building auth systems as a self-hosted alternative to Clerk or Auth.js, particularly for Cloudflare Workers projects. Supports social providers (Google, GitHub, Microsoft, Apple), email/password, magic links, 2FA, passkeys, organizations, and RBAC. Prevents 10+ common authentication errors including session serialization issues, CORS misconfigurations, D1 adapter setup, social provider OAuth flows, and JWT token handling. Keywords: better-auth, authentication, cloudflare d1 auth, self-hosted auth, typescript auth, clerk alternative, auth.js alternative, social login, oauth providers, session management, jwt tokens, 2fa, two-factor, passkeys, webauthn, multi-tenant auth, organizations, teams, rbac, role-based access, google auth, github auth, microsoft auth, apple auth, magic links, email password, better-auth setup, session serialization error, cors auth, d1 adapter

Install

npx vskill@latest install jackspace/claudeskillz/better-auth

SKILL.md

better-auth Skill

Overview

better-auth is a comprehensive, framework-agnostic authentication and authorization library for TypeScript. It provides a complete auth solution with first-class support for Cloudflare D1, making it an excellent self-hosted alternative to Clerk or Auth.js.

Use this skill when:

  • Building authentication for Cloudflare Workers + D1 applications
  • Need a self-hosted, vendor-independent auth solution
  • Migrating from Clerk (avoid vendor lock-in)
  • Upgrading from Auth.js (need more features)
  • Implementing multi-tenant SaaS with organizations/teams
  • Require advanced features: 2FA, passkeys, RBAC, social auth

Package: better-auth@1.3.34 (latest verified 2025-10-31)


Installation

Core Package
bash
npm install better-auth
# or
pnpm add better-auth
# or
yarn add better-auth
Database Adapters

For Cloudflare D1 (Workers):

bash
npm install @cloudflare/workers-types

For PostgreSQL:

bash
npm install pg drizzle-orm

For MySQL/SQLite: Built-in adapters, no extra packages needed.

Social Providers (Optional)
bash
npm install @better-auth/google
npm install @better-auth/github
npm install @better-auth/microsoft

Quick Start Patterns

Pattern 1: Cloudflare Workers + D1

Use when: Building API on Cloudflare Workers with D1 database

File: src/worker.ts

typescript
import { betterAuth } from 'better-auth'
import { d1Adapter } from 'better-auth/adapters/d1'
import { Hono } from 'hono'
type Env = {
DB: D1Database
BETTER_AUTH_SECRET: string
GOOGLE_CLIENT_ID: string
GOOGLE_CLIENT_SECRET: string
}
const app = new Hono<{ Bindings: Env }>()
// Auth routes handler
app.all('/api/auth/*', async (c) => {
const auth = betterAuth({
database: d1Adapter(c.env.DB),
secret: c.env.BETTER_AUTH_SECRET,
// Basic auth methods
emailAndPassword: {
enabled: true,
requireEmailVerification: true
},
// Social providers
socialProviders: {
google: {
clientId: c.env.GOOGLE_CLIENT_ID,
clientSecret: c.env.GOOGLE_CLIENT_SECRET
}
}
})
return auth.handler(c.req.raw)
})
export default app

wrangler.toml:

toml
name = "my-app"
main = "src/worker.ts"
compatibility_date = "2024-01-01"
[[d1_databases]]
binding = "DB"
database_name = "my-app-db"
database_id = "your-database-id"
[vars]
# Public vars here
# Secrets (use: wrangler secret put BETTER_AUTH_SECRET)
# - BETTER_AUTH_SECRET
# - GOOGLE_CLIENT_ID
# - GOOGLE_CLIENT_SECRET

Setup D1 Database:

bash
# Create database
wrangler d1 create my-app-db
# Generate migration SQL from better-auth
npx better-auth migrate --database d1
# Apply migration
wrangler d1 execute my-app-db --remote --file migrations/0001_initial.sql

Pattern 2: Next.js API Route

Use when: Building traditional Next.js app with PostgreSQL or D1

File: src/lib/auth.ts

typescript
import { betterAuth } from 'better-auth'
import { Pool } from 'pg'
export const auth = betterAuth({
database: new Pool({
connectionString: process.env.DATABASE_URL
}),
secret: process.env.BETTER_AUTH_SECRET!,
emailAndPassword: {
enabled: true,
requireEmailVerification: true,
sendVerificationEmail: async ({ user, url }) => {
// Send email with verification link
await sendEmail({
to: user.email,
subject: 'Verify your email',
html: `Click <a href="${url}">here</a> to verify`
})
}
},
socialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!
},
github: {
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!
}
},
// Advanced features via plugins
plugins: [
twoFactor(),
organization(),
rateLimit()
]
})

File: src/app/api/auth/[...all]/route.ts

typescript
import { auth } from '@/lib/auth'
export const GET = auth.handler
export const POST = auth.handler

Pattern 3: React Client Integration

Use when: Need client-side auth state and actions

File: src/lib/auth-client.ts

typescript
import { createAuthClient } from 'better-auth/client'
export const authClient = createAuthClient({
baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000'
})

File: src/components/LoginForm.tsx

typescript
'use client'
import { authClient } from '@/lib/auth-client'
import { useState } from 'react'
export function LoginForm() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
const { data, error } = await authClient.signIn.email({
email,
password
})
if (error) {
console.error('Login failed:', error)
return
}
// Redirect or update UI
window.location.href = '/dashboard'
}
const handleGoogleSignIn = async () => {
await authClient.signIn.social({
provider: 'google',
callbackURL: '/dashboard'
})
}
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
<button type="submit">Sign In</button>
<button type="button" onClick={handleGoogleSignIn}>
Sign in with Google
</button>
</form>
)
}

Use React Hook (if you have a session endpoint):

typescript
'use client'
import { useSession } from 'better-auth/client'
export function UserProfile() {
const { data: session, isPending } = useSession()
if (isPending) return <div>Loading...</div>
if (!session) return <div>Not authenticated</div>
return (
<div>
<p>Welcome, {session.user.email}</p>
<button onClick={() => authClient.signOut()}>
Sign Out
</button>
</div>
)
}

Pattern 4: Protected API Route (Middleware)

Use when: Need to verify session in API routes

Cloudflare Workers:

typescript
import { betterAuth } from 'better-auth'
import { d1Adapter } from 'better-auth/adapters/d1'
app.get('/api/protected', async (c) => {
const auth = betterAuth({
database: d1Adapter(c.env.DB),
secret: c.env.BETTER_AUTH_SECRET
})
const session = await auth.getSession(c.req.raw)
if (!session) {
return c.json({ error: 'Unauthorized' }, 401)
}
return c.json({
message: 'Protected data',
user: session.user
})
})

Next.js Middleware:

typescript
// middleware.ts
import { NextRequest, NextResponse } from 'next/server'
import { auth } from '@/lib/auth'
export async function middleware(request: NextRequest) {
const session = await auth.getSession(request)
if (!session && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: ['/dashboard/:path*']
}

Advanced Features

Two-Factor Authentication (2FA)
typescript
import { betterAuth } from 'better-auth'
import { twoFactor } from 'better-auth/plugins'
export const auth = betterAuth({
database: /* ... */,
plugins: [
twoFactor({
methods: ['totp', 'sms'], // Time-based or SMS
issuer: 'MyApp'
})
]
})

Client:

typescript
// Enable 2FA for user
const { data, error } = await authClient.twoFactor.enable({
method: 'totp'
})
// Verify code
await authClient.twoFactor.verify({
code: '123456'
})

Organizations & Teams
typescript
import { betterAuth } from 'better-auth'
import { organization } from 'better-auth/plugins'
export const auth = betterAuth({
database: /* ... */,
plugins: [
organization({
roles: ['owner', 'admin', 'member'],
permissions: {
admin: ['read', 'write', 'delete'],
member: ['read']
}
})
]
})

Client:

typescript
// Create organization
await authClient.organization.create({
name: 'Acme Corp',
slug: 'acme'
})
// Invite member
await authClient.organization.inviteMember({
organizationId: 'org_123',
email: 'user@example.com',
role: 'member'
})
// Check permissions
const canDelete = await authClient.organization.hasPermission({
organizationId: 'org_123',
permission: 'delete'
})

Multi-Tenant SaaS
typescript
import { betterAuth } from 'better-auth'
import { multiTenant } from 'better-auth/plugins'
export const auth = betterAuth({
database: /* ... */,
plugins: [
multiTenant({
tenantIdHeader: 'x-tenant-id',
isolateData: true // Ensure tenant data isolation
})
]
})

Rate Limiting
typescript
import { betterAuth } from 'better-auth'
import { rateLimit } from 'better-auth/plugins'
export const auth = betterAuth({
database: /* ... */,
plugins: [
rateLimit({
window: 60, // 60 seconds
max: 5, // 5 requests per window
storage: 'database' // or 'memory'
})
]
})

For Cloudflare: Use KV for distributed rate limiting:

typescript
import { rateLimit } from 'better-auth/plugins'
plugins: [
rateLimit({
window: 60,
max: 5,
storage: {
get: async (key) => {
return await c.env.RATE_LIMIT_KV.get(key)
},
set: async (key, value, ttl) => {
await c.env.RATE_LIMIT_KV.put(key, value, { expirationTtl: ttl })
}
}
})
]

Database Setup

D1 Schema Migration
bash
# Generate migration
npx better-auth migrate --database d1
# This creates: migrations/0001_initial.sql

Apply migration:

bash
# Local
wrangler d1 execute my-app-db --local --file migrations/0001_initial.sql
# Production
wrangler d1 execute my-app-db --remote --file migrations/0001_initial.sql

Manual schema (if needed):

sql
-- better-auth core tables
CREATE TABLE users (
id TEXT PRIMARY KEY,
email TEXT UNIQUE NOT NULL,
emailVerified INTEGER DEFAULT 0,
name TEXT,
image TEXT,
createdAt INTEGER NOT NULL,
updatedAt INTEGER NOT NULL
);
CREATE TABLE sessions (
id TEXT PRIMARY KEY,
userId TEXT NOT NULL,
expiresAt INTEGER NOT NULL,
ipAddress TEXT,
userAgent TEXT,
FOREIGN KEY (userId) REFERENCES users(id) ON DELETE CASCADE
);
CREATE TABLE accounts (
id TEXT PRIMARY KEY,
userId TEXT NOT NULL,
provider TEXT NOT NULL,
providerAccountId TEXT NOT NULL,
accessToken TEXT,
refreshToken TEXT,
expiresAt INTEGER,
FOREIGN KEY (userId) REFERENCES users(id) ON DELETE CASCADE
);
CREATE TABLE verification_tokens (
identifier TEXT NOT NULL,
token TEXT NOT NULL,
expires INTEGER NOT NULL,
PRIMARY KEY (identifier, token)
);
-- Additional tables for plugins (organizations, 2FA, etc.)

PostgreSQL with Drizzle

File: src/db/schema.ts

typescript
import { pgTable, text, timestamp, boolean } from 'drizzle-orm/pg-core'
export const users = pgTable('users', {
id: text('id').primaryKey(),
email: text('email').unique().notNull(),
emailVerified: boolean('email_verified').default(false),
name: text('name'),
image: text('image'),
createdAt: timestamp('created_at').notNull().defaultNow(),
updatedAt: timestamp('updated_at').notNull().defaultNow()
})
// ... other tables

Setup:

typescript
import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'
import { betterAuth } from 'better-auth'
const client = postgres(process.env.DATABASE_URL!)
const db = drizzle(client)
export const auth = betterAuth({
database: db,
// ...
})

Social Provider Setup

Google OAuth

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 62 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
14
Repository forks
6
7-day trend score
14

Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 25 Apr 2026, 6 months ago.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/jackspace/claudeskillz/better-auth/badge)](https://verified-skill.com/skills/jackspace/claudeskillz/better-auth)

Something wrong with this skill or its page? Tell us.