better-auth
Self-hosted TypeScript auth for Cloudflare Workers with OAuth, 2FA, passkeys, organizations, and 80+ production-ready endpoints.
Works with
0
total installs
0
this week
695
GitHub stars
0
upvotes
Install Skill
Run in your terminal
0
installs
0
this week
695
stars
What it does
Requires Drizzle ORM or Kysely for D1 (no direct adapter); use drizzleAdapter() or new Kysely({ dialect: new D1Dialect() }) with nodejs_compat flag in wrangler.toml
Provides 80+ auto-generated REST endpoints covering authentication, sessions, 2FA, organizations, admin operations, and social OAuth—zero endpoint code needed
Includes 15+ plugins: OAuth 2.1 Pro
Installation Guide
How to use better-auth on Cursor
AI-first code editor with Composer
Prerequisites
Before installing skills in Cursor, ensure your development environment meets these requirements:
- ›Cursor installed and configured on your machine
- ›Node.js 16+ with npm — verify with
node --version - ›Active project directory where you want to add
better-auth
Run the install command
Execute the skills CLI command in your project's root directory to begin installation:
Fetches better-auth from jezweb/claude-skills and configures it for Cursor.
Select Cursor when prompted
The CLI shows a list of agents. Use arrow keys and space to select Cursor:
Verify installation
Confirm successful installation by checking the skill directory location:
Restart Cursor to activate better-auth. Access via /better-auth in your agent's command palette.
Security Notice
We perform automated surface-level scans (Gen AI Scanner, Socket, Snyk) during installation. These checks detect common vulnerabilities but do not guarantee complete security. Always review skill source code and verify the publisher's reputation before production use.
Skills execute code in your environment. Always review source, verify the publisher, and test in isolation before production.
Documentation
better-auth - D1 Adapter & Error Prevention Guide
Package: [email protected] (Jan 21, 2026) Breaking Changes: ESM-only (v1.4.0), Admin impersonation prevention default (v1.4.6), Multi-team table changes (v1.3), D1 requires Drizzle/Kysely (no direct adapter)
⚠️ CRITICAL: D1 Adapter Requirement
better-auth DOES NOT have d1Adapter(). You MUST use:
- Drizzle ORM (recommended):
drizzleAdapter(db, { provider: "sqlite" }) - Kysely:
new Kysely({ dialect: new D1Dialect({ database: env.DB }) })
See Issue #1 below for details.
What's New in v1.4.10 (Dec 31, 2025)
Major Features:
- OAuth 2.1 Provider plugin - Build your own OAuth provider (replaces MCP plugin)
- Patreon OAuth provider - Social sign-in with Patreon
- Kick OAuth provider - With refresh token support
- Vercel OAuth provider - Sign in with Vercel
- Global
backgroundTasksconfig - Deferred actions for better performance - Form data support - Email authentication with fetch metadata fallback
- Stripe enhancements - Flexible subscription lifecycle,
disableRedirectoption
Admin Plugin Updates:
- ⚠️ Breaking: Impersonation of admins disabled by default (v1.4.6)
- Support role with permission-based user updates
- Role type inference improvements
Security Fixes:
- SAML XML parser hardening with configurable size constraints
- SAML assertion timestamp validation with per-provider clock skew
- SSO domain-verified provider trust
- Deprecated algorithm rejection
- Line nonce enforcement
📚 Docs: https://www.better-auth.com/changelogs
What's New in v1.4.0 (Nov 22, 2025)
Major Features:
- Stateless session management - Sessions without database storage
- ESM-only package ⚠️ Breaking: CommonJS no longer supported
- JWT key rotation - Automatic key rotation for enhanced security
- SCIM provisioning - Enterprise user provisioning protocol
- @standard-schema/spec - Replaces ZodType for validation
- CaptchaFox integration - Built-in CAPTCHA support
- Automatic server-side IP detection
- Cookie-based account data storage
- Multiple passkey origins support
- RP-Initiated Logout endpoint (OIDC)
📚 Docs: https://www.better-auth.com/changelogs
What's New in v1.3 (July 2025)
Major Features:
- SSO with SAML 2.0 - Enterprise single sign-on (moved to separate
@better-auth/ssopackage) - Multi-team support ⚠️ Breaking:
teamIdremoved from member table, newteamMemberstable required - Additional fields - Custom fields for organization/member/invitation models
- Performance improvements and bug fixes
📚 Docs: https://www.better-auth.com/blog/1-3
Alternative: Kysely Adapter Pattern
If you prefer Kysely over Drizzle:
File: src/auth.ts
import { betterAuth } from "better-auth";
import { Kysely, CamelCasePlugin } from "kysely";
import { D1Dialect } from "kysely-d1";
type Env = {
DB: D1Database;
BETTER_AUTH_SECRET: string;
// ... other env vars
};
export function createAuth(env: Env) {
return betterAuth({
secret: env.BETTER_AUTH_SECRET,
// Kysely with D1Dialect
database: {
db: new Kysely({
dialect: new D1Dialect({
database: env.DB,
}),
plugins: [
// CRITICAL: Required if using Drizzle schema with snake_case
new CamelCasePlugin(),
],
}),
type: "sqlite",
},
emailAndPassword: {
enabled: true,
},
// ... other config
});
}
Why CamelCasePlugin?
If your Drizzle schema uses snake_case column names (e.g., email_verified), but better-auth expects camelCase (e.g., emailVerified), the CamelCasePlugin automatically converts between the two.
⚠️ Cloudflare Workers Note: D1 database bindings are only available inside the request handler (the fetch() function). You cannot initialize better-auth outside the request context. Use a factory function pattern:
// ❌ WRONG - DB binding not available outside request
const db = drizzle(env.DB, { schema }) // env.DB doesn't exist here
export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "sqlite" }) })
// ✅ CORRECT - Create auth instance per-request
export default {
fetch(request, env, ctx) {
const db = drizzle(env.DB, { schema })
const auth = betterAuth({ database: drizzleAdapter(db, { provider: "sqlite" }) })
return auth.handler(request)
}
}
Community Validation: Multiple production implementations confirm this pattern (Medium, AnswerOverflow, official Hono examples).
Framework Integrations
TanStack Start
⚠️ CRITICAL: TanStack Start requires the reactStartCookies plugin to handle cookie setting properly.
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { reactStartCookies } from "better-auth/react-start";
export const auth = betterAuth({
database: drizzleAdapter(db, { provider: "sqlite" }),
plugins: [
twoFactor(),
organization(),
reactStartCookies(), // ⚠️ MUST be LAST plugin
],
});
Why it's needed: TanStack Start uses a special cookie handling system. Without this plugin, auth functions like signInEmail() and signUpEmail() won't set cookies properly, causing authentication to fail.
Important: The reactStartCookies plugin must be the last plugin in the array.
Session Nullability Pattern: When using useSession() in TanStack Start, the session object always exists, but session.user and session.session are null when not logged in:
const { data: session } = authClient.useSession()
// When NOT logged in:
console.log(session) // { user: null, session: null }
console.log(!!session) // true (unexpected!)
// Correct check:
if (session?.user) {
// User is logged in
}
Always check session?.user or session?.session, not just session. This is expected behavior (session object container always exists).
API Route Setup (/src/routes/api/auth/$.ts):
import { auth } from '@/lib/auth'
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/api/auth/$')({
server: {
handlers: {
GET: ({ request }) => auth.handler(request),
POST: ({ request }) => auth.handler(request),
},
},
})
📚 Official Docs: https://www.better-auth.com/docs/integrations/tanstack
Available Plugins (v1.4+)
Better Auth provides plugins for advanced authentication features: