Skill for integrating Better Auth - comprehensive TypeScript authentication framework for Cloudflare D1, Next.js, Nuxt, and 15+ frameworks. Use when adding auth, encountering D1 adapter errors, or implementing OAuth/2FA/RBAC features.
npx skills add https://github.com/secondsky/claude-skills --skill better-auth
Status: Production Ready
Last Updated: 2026-04-08
Package: [email protected] (ESM-only)
Dependencies: Drizzle ORM or Kysely (required for D1 complex use cases; D1 native support available in v1.5+)
Option 1: Drizzle ORM (Recommended)
bun add better-auth drizzle-orm drizzle-kit
Option 2: Kysely
bun add better-auth kysely @noxharmonium/kysely-d1
better-auth v1.4.0+ is ESM-only. Ensure:
package.json:
{
"type": "module"
}
Upgrading from v1.3.x? Load references/migration-guide-1.4.0.md
Upgrading from v1.4.x? Load references/migration-guide-1.5.0.md
v1.5.0+: D1 is now natively supported. Pass your D1 binding directly:
// ✅ SIMPLEST - D1 native (v1.5.0+)
import { betterAuth } from "better-auth";
const auth = betterAuth({
database: env.DB, // D1 binding, auto-detected
});
For complex schemas, use Drizzle ORM:
// ✅ RECOMMENDED for complex schemas - Drizzle
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { drizzle } from "drizzle-orm/d1";
const auth = betterAuth({
database: drizzleAdapter(drizzle(env.DB, { schema }), { provider: "sqlite" }),
});
// ❌ WRONG - This doesn't exist
import { d1Adapter } from 'better-auth/adapters/d1'
1. Create D1 Database:
wrangler d1 create my-app-db
2. Define Schema (src/db/schema.ts):
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
export const user = sqliteTable("user", {
id: text().primaryKey(),
name: text().notNull(),
email: text().notNull().unique(),
emailVerified: integer({ mode: "boolean" }).notNull().default(false),
image: text(),
});
export const session = sqliteTable("session", {
id: text().primaryKey(),
userId: text().notNull().references(() => user.id, { onDelete: "cascade" }),
token: text().notNull(),
expiresAt: integer({ mode: "timestamp" }).notNull(),
});
// See references/database-schema.ts for complete schema
3. Initialize Auth (src/auth.ts):
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { drizzle } from "drizzle-orm/d1";
import * as schema from "./db/schema";
export function createAuth(env: { DB: D1Database; BETTER_AUTH_SECRET: string }) {
const db = drizzle(env.DB, { schema });
return betterAuth({
baseURL: env.BETTER_AUTH_URL,
secret: env.BETTER_AUTH_SECRET,
database: drizzleAdapter(db, { provider: "sqlite" }),
emailAndPassword: { enabled: true },
});
}
4. Create Worker (src/index.ts):
import { Hono } from "hono";
import { createAuth } from "./auth";
const app = new Hono<{ Bindings: Env }>();
app.all("/api/auth/*", async (c) => {
const auth = createAuth(c.env);
return auth.handler(c.req.raw);
});
export default app;
5. Deploy:
bunx drizzle-kit generate
wrangler d1 migrations apply my-app-db --remote
wrangler deploy
For code examples and syntax, always consult better-auth.com/docs.
Is this a new/empty project?
├─ YES → New project setup
│ 1. Identify framework (Next.js, Nuxt, Workers, etc.)
│ 2. Choose database (D1, PostgreSQL, MongoDB, MySQL)
│ 3. Install better-auth + Drizzle/Kysely
│ 4. Create auth.ts + auth-client.ts
│ 5. Set up route handler (see Quick Start above)
│ 6. Run migrations (Drizzle Kit for D1)
│ 7. Add features via plugins (2FA, organizations, etc.)
│
└─ NO → Does project have existing auth?
├─ YES → Migration/enhancement
│ • Audit current auth for gaps
│ • Plan incremental migration
│ • See references/framework-comparison.md for migration guides
│
└─ NO → Add auth to existing project
1. Analyze project structure
2. Install better-auth + adapter
3. Create auth config (see Quick Start)
4. Add route handler to existing routes
5. Run schema migrations
6. Integrate into existing pages/components
✅ Use better-auth/minimal + adapter packages for smallest bundle (v1.5+)
✅ Use npx auth migrate and npx auth generate for CLI commands (v1.5+)
✅ Set BETTER_AUTH_SECRET via wrangler secret put
✅ Configure CORS with credentials: true
✅ Match OAuth callback URLs exactly (no trailing slash)
✅ Apply migrations to local D1 before wrangler dev
✅ Use camelCase column names in schema
❌ Use d1Adapter (doesn't exist)
❌ Forget CORS credentials or mismatch OAuth URLs
❌ Use snake_case columns without CamelCasePlugin
❌ Skip local migrations or hardcode secrets
❌ Leave sendVerificationEmail unimplemented
API Key Plugin Moved:
- import { apiKey } from "better-auth/plugins";
+ import { apiKey } from "@better-auth/api-key";
Schema: userId → referenceId, new configId field.
After Hooks: Database after-hooks now run post-transaction (not inside it).
Deprecated APIs Removed: Adapter → DBAdapter, InferUser/InferSession removed, @better-auth/core/utils split into subpath exports.
Load references/migration-guide-1.5.0.md when upgrading from <1.5.0
Session Freshness: freshAge now uses createdAt (not updatedAt). Sessions may require re-auth more frequently for sensitive operations.
SAML Security: InResponseTo validation is default ON. Opt out with saml: { enableInResponseToValidation: false }.
OIDC Provider Deprecated: Use @better-auth/oauth-provider instead.
npx auth init/migrate/generate/upgrade@better-auth/oauth-provider (MCP-ready)@better-auth/electron for desktop apps@better-auth/i18n for error translationscode in error responsesLoad references/v1.5-features.md for detailed implementation guides.
mode: "insensitive" on adapter queriesLoad references/v1.6-features.md for detailed implementation guides.
| Variable | Purpose | Example |
|----------|---------|---------|
| BETTER_AUTH_SECRET | Encryption secret (min 32 chars) | Generate: openssl rand -base64 32 |
| BETTER_AUTH_URL | Base URL | https://example.com or http://localhost:8787 |
| DATABASE_URL | Database connection (optional for D1) | PostgreSQL/MySQL connection string |
Note: Only define baseURL/secret in config if env vars are NOT set.
| Command | Purpose |
|---------|---------|
| npx auth init | Interactive project scaffolding |
| npx auth migrate | Run database migrations |
| npx auth generate | Generate auth schema |
| npx auth generate --adapter drizzle | Adapter-specific schema |
| npx auth upgrade | Upgrade to latest version |
| bunx drizzle-kit generate | D1: Use this to generate Drizzle migrations |
| wrangler d1 migrations apply DB_NAME | D1: Use this to apply migrations |
Re-run after adding/changing plugins.
| Option | Notes |
|--------|-------|
| appName | Optional display name |
| baseURL | Only if BETTER_AUTH_URL not set |
| basePath | Default /api/auth. Set / for root. |
| secret | Only if BETTER_AUTH_SECRET not set (min 32 chars) |
| database | Required for most features. Use drizzleAdapter() or Kysely for D1 |
| secondaryStorage | Redis/KV for sessions & rate limits |
| emailAndPassword | { enabled: true } to activate |
| socialProviders | { google: { clientId, clientSecret }, ... } |
| plugins | Array of plugins (import from dedicated paths) |
| trustedOrigins | CSRF whitelist for cross-origin requests |
Import from dedicated packages (extracted in v1.5+):
import { twoFactor } from "better-auth/plugins/two-factor"
import { organization } from "better-auth/plugins/organization"
import { passkey } from "@better-auth/passkey" // Separate package
import { apiKey } from "@better-auth/api-key" // Separate package (v1.5+)
import { sso } from "@better-auth/sso" // Separate package (v1.5+)
import { i18n } from "@better-auth/i18n" // Separate package (v1.5+)
import { oauthProvider } from "@better-auth/oauth-provider" // Separate package (v1.5+)
Core plugins (still in better-auth/plugins): twoFactor, organization, admin, anonymous, emailOTP, magicLink, phone-number, multi-session, custom-session.
Problem: Trying to use non-existent d1Adapter
Solution: Use drizzleAdapter or Kysely instead (see Quick Start above)
Problem: better-auth migrate doesn't work with D1
Solution: Use bunx drizzle-kit generate then wrangler d1 migrations apply
Problem: Database uses email_verified but better-auth expects emailVerified
Solution: Use camelCase in schema or add CamelCasePlugin to Kysely
Problem: Access-Control-Allow-Origin errors, cookies not sent
Solution: Configure CORS with credentials: true and correct origins
Problem: Social sign-in fails with "redirect_uri_mismatch"
Solution: Ensure exact match: https://yourdomain.com/api/auth/callback/google
Load references/error-catalog.md for all 15 errors with detailed solutions.
When: Basic authentication without social providers
Quick Pattern:
// Client
await authClient.signIn.email({
email: "[email protected]",
password: "password123",
});
// Server - enable in config
emailAndPassword: {
enabled: true,
requireEmailVerification: true,
}
Load: references/setup-guide.md → Step 5
When: Allow users to sign in with social accounts
Supported: Google, GitHub, Microsoft, Apple, Discord, TikTok, Twitch, Spotify, LinkedIn, Slack, Reddit, Facebook, Twitter/X, Patreon, Vercel, Kick, and 30+ more.
Quick Pattern:
// Client
await authClient.signIn.social({
provider: "google",
callbackURL: "/dashboard",
});
// Server config
socialProviders: {
google: {
clientId: env.GOOGLE_CLIENT_ID,
clientSecret: env.GOOGLE_CLIENT_SECRET,
scope: ["openid", "email", "profile"],
},
}
Load: references/setup-guide.md → Step 5
When: Need to verify user is authenticated
Quick Pattern:
app.get("/api/protected", async (c) => {
const auth = createAuth(c.env);
const session = await auth.api.getSession({
headers: c.req.raw.headers,
});
if (!session) {
return c.json({ error: "Unauthorized" }, 401);
}
return c.json({ data: "protected", user: session.user });
});
Load: references/cloudflare-worker-drizzle.ts
When: Building SaaS with teams/organizations
Load: references/advanced-features.md → Organizations & Teams
When: Need extra security with 2FA/TOTP
Load: references/advanced-features.md → Two-Factor Authentication
Load references/setup-guide.md when:
Load references/error-catalog.md when:
Load references/advanced-features.md when:
Load references/v1.5-features.md when:
Load references/v1.6-features.md when:
Load references/migration-guide-1.5.0.md when:
userId → referenceId)InferUser/InferSession type errors@better-auth/core/utils importsLoad references/plugins/sso.md when:
Load references/plugins/test-utils.md when:
Load references/integrations/electron.md when:
Load references/cloudflare-worker-drizzle.ts when:
Load references/cloudflare-worker-kysely.ts when:
Load references/database-schema.ts when:
Load references/react-client-hooks.tsx when:
Load references/configuration-guide.md when:
Load references/framework-comparison.md when:
Load references/migration-guide-1.4.0.md when:
forgetPassword errors or ESM issuesLoad references/v1.4-features.md when:
Load references/nextjs/README.md when:
Load references/nextjs/postgres-example.ts when:
Load references/frameworks/nextjs.md when:
Load references/frameworks/nuxt.md when:
Load references/frameworks/remix.md when:
Load references/frameworks/sveltekit.md when:
Load references/frameworks/api-frameworks.md when:
Load references/frameworks/expo-mobile.md when:
Load references/databases/postgresql.md when:
Load references/databases/mongodb.md when:
Load references/databases/mysql.md when:
Load references/plugins/authentication.md when:
Load references/plugins/enterprise.md when:
Load references/plugins/api-tokens.md when:
Load references/plugins/payments.md when:
Load references/plugins/sso.md when:
Load references/plugins/test-utils.md when:
Load references/integrations/electron.md when:
Quick Config (ESM-only in v1.4.0+):
export const auth = betterAuth({
baseURL: env.BETTER_AUTH_URL,
secret: env.BETTER_AUTH_SECRET,
database: drizzleAdapter(db, { provider: "sqlite" }),
});
Load references/configuration-guide.md for:
Create auth client (src/lib/auth-client.ts):
import { createAuthClient } from "better-auth/client";
export const authClient = createAuthClient({
baseURL: import.meta.env.VITE_API_URL || "http://localhost:8787",
});
Use in React:
import { authClient } from "@/lib/auth-client";
export function UserProfile() {
const { data: session, isPending } = authClient.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>
);
}
Required:
better-auth@^1.6.0 - Core authentication framework (ESM-only)Choose ONE adapter (optional with D1 native in v1.5+):
drizzle-orm@^0.44.7 + drizzle-kit@^0.31.7 (recommended for complex schemas)kysely@^0.28.8 + @noxharmonium/kysely-d1@^0.4.0 (alternative)@better-auth/drizzle-adapter + better-auth/minimal (smallest bundle, v1.5+)Optional:
@cloudflare/workers-types - TypeScript types for Workershono@^4.0.0 - Web framework for routing@better-auth/passkey - Passkey/WebAuthn plugin@better-auth/api-key - API key auth with org support@better-auth/sso - SSO/SAML/OIDC production plugin@better-auth/electron - Electron desktop auth@better-auth/i18n - Error message translations@better-auth/oauth-provider - OAuth 2.1 authorization serverThis skill focuses on Cloudflare Workers + D1. better-auth also supports:
Frameworks (18 total): Next.js, Nuxt, Remix, SvelteKit, Astro, Express, NestJS, Fastify, Elysia, Expo, and more.
Databases (9 adapters): PostgreSQL, MongoDB, MySQL, Prisma, MS SQL, and others.
Additional Plugins: Anonymous auth, Email OTP, JWT, Multi-Session, OAuth 2.1 Provider, Test Utils, SCIM, payment integrations (Stripe, Polar), Device Authorization.
For non-Cloudflare setups, load the appropriate framework or database reference file, or consult the official docs: https://better-auth.com/docs
Load references/framework-comparison.md for:
Verified working repositories (all use Drizzle or Kysely):
Note: Check each repo's better-auth version. Repos on v1.3.x need v1.4.0+ migration (see references/migration-guide-1.4.0.md). None use a direct d1Adapter - all require Drizzle/Kysely.
When installing authentication packages, follow supply chain security best practices — auth libraries are high-value targets for supply chain attacks:
npm config set ignore-scripts true (or Bun: disabled by default)socket package score npm <pkg> or use socket npm install <pkg> to check packagesLoad the dependency-upgrade skill for full security configuration including Socket CLI integration, cooldown setup, lockfile validation, and CI enforcement.
"type": "module" in package.json) - v1.4.0+ requirednpx auth CLI (not @better-auth/cli) - v1.5.0+@better-auth/api-key (not better-auth/plugins) for API keys - v1.5.0+Questions? Issues?
references/error-catalog.md for all 15 errors and solutionsreferences/setup-guide.md for complete 8-step setupreferences/advanced-features.md for 2FA, organizations, and moreYou are a dependency security expert specializing in vulnerability scanning, license compliance, and supply chain security. Analyze project dependencies for known vulnerabilities, licensing issues, outdated packages, and provide actionable remediation strategies.
Perform language and framework specific security best-practice reviews and suggest improvements. Trigger only when the user explicitly requests security best practices guidance, a security review/report, or secure-by-default coding help. Trigger only for supported languages (python, javascript/typescript, go). Do not trigger for general code review, debugging, or non-security tasks.
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.
Package entire code repositories into single AI-friendly files using Repomix. Capabilities include pack codebases with customizable include/exclude patterns, generate multiple output formats (XML, Markdown, plain text), preserve file structure and context, optimize for AI consumption with token counting, filter by file types and directories, add custom headers and summaries. Use when packaging codebases for AI analysis, creating repository snapshots for LLM context, analyzing third-party libraries, preparing for security audits, generating documentation context, or evaluating unfamiliar codebases.
You are a dependency security expert specializing in vulnerability scanning, license compliance, and supply chain security. Analyze project dependencies for known vulnerabilities, licensing issues, outdated packages, and provide actionable remediation strategies.
Expert patterns for HubSpot CRM integration including OAuth authentication, CRM objects, associations, batch operations, webhooks, and custom objects. Covers Node.js and Python SDKs.
Perform language and framework specific security best-practice reviews and suggest improvements. Use when the user explicitly requests security best practices guidance, a security review or report, or secure-by-default coding help. Supports Python, JavaScript/TypeScript, and Go. Do NOT use for general code review, debugging, threat modeling (use security-threat-model), or non-security tasks.
Configures API gateways for routing, authentication, rate limiting, and request transformation in microservice architectures. Use when setting up Kong, Nginx, AWS API Gateway, or Traefik for centralized API management.
Take secondsky/better-auth from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference npm, npx.
Without those the skill loads but fails at the first command.