Claude-skill-registry better-auth-v2
Production-ready authentication system using Better Auth v2 with latest features. Includes OAuth providers, advanced RBAC, multi-tenant support, and security best practices.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/better-auth-v2" ~/.claude/skills/majiayu000-claude-skill-registry-better-auth-v2 && rm -rf "$T"
skills/data/better-auth-v2/SKILL.mdBetter Auth v2 Integration Skill
Purpose
Provides cutting-edge authentication templates and expert integration guidance using Better Auth v2. This skill analyzes your project architecture and implements a full-featured authentication system including:
- Email/password and OAuth authentication (Google, Apple, Facebook, X, etc.)
- Advanced OAuth token encryption and account linking
- Multi-factor authentication (2FA) support
- Role-Based Access Control (RBAC) with fine-grained permissions
- Multi-tenant architecture with organizations
- Session management with enhanced security
- Production-ready security configurations
- Personalized onboarding flows for user background collection
What This Skill Provides
✅ Automated Implementation Plan: Analyzes project structure (framework, DB, backend) and provides a tailored integration strategy. ✅ Latest Better Auth v2 configuration templates ✅ Enhanced database schemas (Prisma, Drizzle, Kysely, MongoDB) ✅ Modern React/Vue/Svelte authentication clients ✅ Middleware for route protection with advanced features ✅ Fine-grained RBAC and permission utilities ✅ OAuth provider setup with token encryption ✅ 2FA and passkey authentication templates ✅ Advanced security configurations ✅ Separate Backend Creation: For static/SSR-incompatible sites, creates a dedicated Hono + Node.js backend.
What This Skill Does NOT Provide
❌ Custom business logic implementation (use better-auth-specialist subagent) ❌ Database migration scripts execution (provides the scripts, does not run them) ❌ OAuth credential provisioning (you must provide Client IDs/Secrets) ❌ Production deployment without security review
When to Use This Skill
Use this skill when:
- Building new applications with modern authentication requirements
- Migrating from older auth systems to Better Auth v2
- Implementing enterprise-grade security features
- Setting up multi-tenant SaaS applications
- Requiring advanced OAuth features like token encryption
- Needing 2FA or passkey authentication
- User wants personalized onboarding questions after signup
- Project uses Next.js, Remix, Astro, Vite, SvelteKit, Nuxt, or pure React
How to use:
Load the better-auth-v2 skill and use the [template-name] for [framework] with [database-adapter] OR Simply ask: "Add Better-Auth to my project" to start the automated integrator flow.
How This Skill Works (Step-by-Step Execution)
-
Project Analysis Phase
- Detect framework (Next.js App Router, Pages Router, Remix, Astro, SvelteKit, etc.)
- Determine if SSR/SSG/static
- Check for existing backend (Node/Express, tRPC, Hono, etc.)
- Detect existing database setup and ORM (Prisma, Drizzle, TypeORM, raw SQL, etc.)
-
Database Strategy
- If a database is already configured → ask for connection string/credentials (only after explicit user confirmation)
- If no database → ask user preference (PostgreSQL recommended) and guide creation (e.g., Supabase, Neon, Railway, PlanetScale)
- Only after user says “yes, go ahead”, create required Better-Auth tables/schema automatically
-
Backend Setup
- If project supports SSR/API routes → add Better-Auth directly inside the project
- If static or incompatible → create separate backend repo/folder using Hono + Node.js/Express (deployable to Vercel, Railway, Fly.io, etc.)
- Always use Drizzle ORM (Better-Auth’s preferred adapter) with PostgreSQL
-
Better-Auth Core Implementation
- Install
+better-auth
+ required plugins (google, apple, facebook, twitter)better-auth-ui - Configure env variables for all OAuth providers
- Set up email/password + magic links as fallback
- Add user metadata table/extension for background questionnaire answers
- Install
-
Onboarding Flow
- After first successful sign-up → redirect to
/onboarding - Multi-step form asking:
- Years of programming experience
- Primary languages/frameworks
- Hardware (Mac/Windows/Linux, CPU, GPU, RAM)
- Development focus (frontend/backend/full-stack/AI/ML/mobile)
- Store answers in
table for future personalizationuser_metadata
- After first successful sign-up → redirect to
-
UI Integration (Better-Auth-UI)
- Create
,/sign-in
pages with/sign-up<AuthUI /> - Add header component with dynamic auth buttons:
- Guest → “Sign In” | “Sign Up”
- Logged in → Avatar + Dropdown with “Profile”, “Settings”, “Sign Out”
- Fully responsive, accessible, dark mode ready
- Create
-
Session Management & Protection
- Server-side session validation
React hook for frontend stateauthClient- Protected routes middleware (Next.js middleware or route guards)
Output You Will Receive
After activation, I will deliver:
- Complete step-by-step implementation plan tailored to your exact project
- Exact terminal commands to run
- File-by-file code changes/additions
with all required variables.env.example- Database schema (Drizzle migrations)
- Separate backend repo link (if needed)
- Ready-to-copy onboarding questionnaire component
- Header component with conditional auth UI
- Fully working, production-ready authentication system
Example Usage
User says:
“I have a Next.js 14 App Router site using Prisma + PostgreSQL on Supabase. Add Better-Auth with Google, Apple, Facebook, X login and ask users about their dev experience after signup.”
This Skill Instantly Activates → Delivers:
- Confirmed DB usage (no new DB needed)
- Prisma → Drizzle migration plan (or dual-ORM strategy if preferred)
folder structure with full Better-Auth config/src/auth- All OAuth callbacks configured
using/app/(auth)/sign-in/[[...better-auth]]/page.tsx<AuthUI />- Header with dynamic auth state
with questionnaire/app/onboarding/page.tsx- Protected route example using middleware
User says:
“My site is a static Astro + React site. Just add login with social providers.”
This Skill Responds:
→ Creates separate
better-auth-backend/ folder (Hono + Node)→ Deploys in < 2 minutes to Vercel
→ Adds minimal client
authClient to Astro→ Injects header buttons + modal sign-in using Better-Auth-UI
→ Full social login working on a 100% static frontend
Activate This Skill By Saying
- “Add Better-Auth to my project”
- “Implement signup and login with Google/Apple/Facebook/X”
- “I want Better-Auth with onboarding questions”
- “Set up authentication for my [Next.js/Astro/Remix/etc.] site”
Available Templates
Configuration Templates
-
Production Auth Config v2 (
)config/production-auth-v2.template.ts- Latest Better Auth v2 features
- OAuth token encryption enabled
- Account linking with email validation
- Advanced security defaults
-
Enterprise Auth Config (
)config/enterprise-auth.template.ts- Multi-tenant with organizations
- Custom roles and permissions
- 2FA enforcement options
- Audit logging ready
-
OAuth-First Config (
)config/oauth-first.template.ts- OAuth providers as primary auth
- Account linking across providers
- Encrypted token storage
- Social login preferences
-
B2B SaaS Config (
)config/b2b-saas.template.ts- Organization-based auth
- Member invitations
- Role hierarchy management
- Team collaboration features
Database Schema Templates
- Prisma Schema v2 (
)schemas/prisma-v2.template.prisma - Drizzle Schema v2 (
)schemas/drizzle-v2.template.ts - Kysely Schema v2 (
)schemas/kysely-v2.template.ts - MongoDB Schema (
)schemas/mongodb.template.ts
Client-Side Templates
- React Auth Client v2 (
)client/react-auth-client-v2.template.tsx - Vue 3 Auth Client (
)client/vue3-auth-client.template.ts - Svelte Auth Client (
)client/svelte-auth-client.template.ts - TypeScript Auth Utilities (
)client/auth-utils.template.ts
Component Templates
- Modern Sign In Form (
)components/SignInForm-v2.template.tsx - Multi-Step Sign Up (
)components/SignUpFlow.template.tsx - OAuth Provider Grid (
)components/OAuthProviderGrid.template.tsx - 2FA Setup Component (
)components/TwoFactorSetup.template.tsx - Organization Manager (
)components/OrganizationManager.template.tsx - Passkey Enrollment (
)components/PasskeyEnroll.template.tsx - Session Security (
)components/SessionSecurity.template.tsx
Middleware Templates
- Next.js App Router Middleware (
)middleware/nextjs-app-router.template.ts - Advanced RBAC Middleware (
)middleware/rbac-gateway.template.ts - Rate Limiting Middleware (
)middleware/rate-limiter.template.ts - Audit Logging Middleware (
)middleware/audit-logger.template.ts
Utility Templates
- Permission Engine (
)utils/permission-engine.template.ts - Role Manager (
)utils/role-manager.template.ts - Security Validator (
)utils/security-validator.template.ts - OAuth Helper (
)utils/oauth-helper.template.ts
Enhanced Features
OAuth Token Encryption
- Automatic encryption of OAuth tokens at rest
- Secure token storage with customer-controlled encryption keys
- Token refresh without user intervention
Account Linking
- Link multiple OAuth providers to single account
- Email verification for provider linking
- Prevent duplicate accounts with same email
Advanced RBAC
- Hierarchical role system
- Resource-based permissions
- Time-bound role assignments
- Permission inheritance
Multi-Tenancy
- Organization-based isolation
- Cross-organization roles
- Team-based access control
- Organization invitations
Security Enhancements
- CSRF protection with custom tokens
- Advanced rate limiting per endpoint
- IP-based session validation
- Device fingerprinting
2FA Support
- TOTP (Time-based One-Time Password)
- Backup codes generation
- SMS and email 2FA
- Passkey (WebAuthn) support
Quick Start Templates
Template 1: Production Auth Configuration v2
File:
.claude/skills/better-auth-v2/config/production-auth-v2.template.ts
/** * Better Auth v2 - Production-Ready Configuration * * Latest Features: * - OAuth token encryption * - Account linking across providers * - Enhanced security defaults * - Performance optimizations * * Setup: * 1. Copy to `lib/auth.ts` * 2. Configure environment variables * 3. Set up database with schema * 4. Configure OAuth providers */ import { betterAuth } from "better-auth"; import { prismaAdapter } from "better-auth/adapters/prisma"; import { organization, twoFactor, passkey } from "better-auth/plugins"; import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient(); export const auth = betterAuth({ // =========================================== // DATABASE CONFIGURATION // =========================================== database: prismaAdapter(prisma, { provider: "postgresql", }), // =========================================== // SECURITY: SECRET KEY (REQUIRED) // =========================================== secret: process.env.BETTER_AUTH_SECRET || process.env.AUTH_SECRET!, // =========================================== // BASE URL CONFIGURATION // =========================================== baseURL: process.env.BETTER_AUTH_URL || "http://localhost:3000", // =========================================== // EMAIL & PASSWORD AUTHENTICATION // =========================================== emailAndPassword: { enabled: true, requireEmailVerification: true, minPasswordLength: 8, maxPasswordLength: 128, // Enhanced password validation passwordValidation: (password) => { const checks = { length: password.length >= 8 && password.length <= 128, upper: /[A-Z]/.test(password), lower: /[a-z]/.test(password), number: /[0-9]/.test(password), special: /[!@#$%^&*(),.?":{}|<>]/.test(password), noCommon: !password.toLowerCase().includes('password'), noUsername: false, // Will be checked against username/email }; if (Object.values(checks).some(check => !check)) { return { valid: false, message: "Password must be 8-128 characters with uppercase, lowercase, number, and special character", }; } return { valid: true }; }, sendEmailVerificationOnSignUp: true, }, // =========================================== // OAUTH PROVIDERS WITH ENCRYPTION // =========================================== socialProviders: { github: { clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET!, scope: ["user:email"], // Optional: Custom redirect URI // redirectURI: "https://yourapp.com/auth/callback/github" }, 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!, scope: ["identify", "email"], }, microsoft: { clientId: process.env.MICROSOFT_CLIENT_ID!, clientSecret: process.env.MICROSOFT_CLIENT_SECRET!, tenant: "common", scope: ["openid", "email", "profile"], }, // Additional providers /* apple: { clientId: process.env.APPLE_CLIENT_ID!, clientSecret: process.env.APPLE_CLIENT_SECRET!, }, twitter: { clientId: process.env.TWITTER_CLIENT_ID!, clientSecret: process.env.TWITTER_CLIENT_SECRET!, }, facebook: { clientId: process.env.FACEBOOK_CLIENT_ID!, clientSecret: process.env.FACEBOOK_CLIENT_SECRET!, }, */ }, // =========================================== // ACCOUNT MANAGEMENT WITH ENCRYPTION // =========================================== account: { // Model configuration (optional) modelName: "accounts", fields: { userId: "user_id", }, // Encrypt OAuth tokens at rest (SECURITY: Production best practice) encryptOAuthTokens: true, // Store account in cookie for database-less flows storeAccountCookie: false, // Set true only for specific use cases // Account linking configuration accountLinking: { enabled: true, // Trusted providers for auto-linking trustedProviders: ["google", "github", "microsoft", "apple"], // Require same email for linking allowDifferentEmails: false, }, }, // =========================================== // ENHANCED SESSION MANAGEMENT // =========================================== session: { expiresIn: 60 * 60 * 24 * 7, // 7 days updateAge: 60 * 60 * 24, // Update every 24 hours cookieCache: { enabled: true, maxAge: 5 * 60, // 5 minutes }, // Additional session security sessionExpiration: { warning: 60 * 60 * 24, // Warn 1 day before expiration }, }, // =========================================== // ADVANCED SECURITY CONFIGURATION // =========================================== advanced: { // CSRF protection (NEVER disable in production) disableCSRFCheck: false, // Trusted origins trustedOrigins: [ process.env.BETTER_AUTH_URL!, ...(process.env.NODE_ENV === "development" ? ["http://localhost:3000", "http://localhost:5173", "http://localhost:4173"] : [] ), ], // Cookie security defaultCookieAttributes: { sameSite: "lax", secure: process.env.NODE_ENV === "production", httpOnly: true, // Optional: Domain for cross-subdomain cookies // domain: ".yourdomain.com", }, // Cross-subdomain cookies crossSubDomainCookies: { enabled: false, // Enable if using subdomains // domain: ".yourdomain.com", }, }, // =========================================== // RATE LIMITING WITH FINE-GRAINED CONTROL // =========================================== rateLimit: { enabled: true, window: 60, // 1 minute window max: 10, // 10 requests per window // Endpoint-specific limits customRules: [ { pathPattern: "/api/auth/sign-in", window: 60, max: 5, // Stricter for sign-in }, { pathPattern: "/api/auth/sign-up", window: 300, // 5 minutes max: 3, // Prevent mass sign-ups }, { pathPattern: "/api/auth/reset-password", window: 900, // 15 minutes max: 3, // Prevent spam }, { pathPattern: "/api/auth/verify-email", window: 60, max: 10, // Allow more attempts for verification }, ], // IP-based rate limiting ipBasedLimiting: true, // Custom storage for rate limits (Redis recommended for production) // storage: customRateLimitStorage, }, // =========================================== // USER PROFILE CONFIGURATION // =========================================== user: { // Additional user fields additionalFields: { displayName: { type: "string", required: false, }, avatar: { type: "string", required: false, }, bio: { type: "string", required: false, }, preferences: { type: "json", required: false, }, }, // User model configuration modelName: "users", fields: { email: "email_address", name: "full_name", }, }, // =========================================== // PLUGINS FOR ENTERPRISE FEATURES // =========================================== plugins: [ // Organization plugin for multi-tenancy organization({ enabled: true, organizationLimit: 5, // Max organizations per user allowUserToCreateOrganization: true, // Role definitions roles: [ { role: "owner", permissions: ["*"], // Full access }, { role: "admin", permissions: [ "org:*", "member:*", "billing:*", ], }, { role: "member", permissions: [ "org:read", "content:*", ], }, ], // Custom application roles customRoles: [ { role: "moderator", label: "Content Moderator", permissions: [ "content:moderate", "content:delete:any", "user:warn", ], }, { role: "analyst", label: "Data Analyst", permissions: [ "analytics:*", "reports:*", "data:read", ], }, ], }), // Two-factor authentication twoFactor({ // Require 2FA for specific roles requireTwoFactorFor: ["admin", "owner"], // 2FA configuration issuer: process.env.APP_NAME || "MyApp", // Backup codes backupCodes: { enabled: true, count: 10, }, // 2FA methods methods: ["totp", "sms", "email"], }), // Passkey (WebAuthn) support passkey({ enabled: true, // Passkey configuration rpName: process.env.APP_NAME || "MyApp", rpID: process.env.PASSKEY_RP_ID || "localhost", origin: process.env.PASSKEY_ORIGIN || "http://localhost:3000", }), ], // =========================================== // EMAIL SERVICE CONFIGURATION // =========================================== emailProvider: { type: "smtp", config: { host: process.env.SMTP_HOST!, port: parseInt(process.env.SMTP_PORT || "587"), secure: process.env.SMTP_SECURE === "true", auth: { user: process.env.SMTP_USER!, pass: process.env.SMTP_PASSWORD!, }, }, from: { name: process.env.EMAIL_FROM_NAME || "MyApp Team", email: process.env.EMAIL_FROM_ADDRESS || "noreply@myapp.com", }, // Email templates (optional) templates: { "email-verification": { subject: "Verify your email", text: (data) => `Click here to verify: ${data.url}`, html: (data) => `<a href="${data.url}">Verify Email</a>`, }, "password-reset": { subject: "Reset your password", text: (data) => `Click here to reset: ${data.url}`, html: (data) => `<a href="${data.url}">Reset Password</a>`, }, }, }, // =========================================== // WEBHOOKS (Optional) // =========================================== webhooks: { // User events onUserCreated: async (user) => { console.log("User created:", user.id); // Trigger welcome email, analytics, etc. }, onUserDeleted: async (user) => { console.log("User deleted:", user.id); // Cleanup user data, revoke access tokens, etc. }, // Session events onSignIn: async (session) => { console.log("User signed in:", session.userId); // Track login analytics }, onSignOut: async (session) => { console.log("User signed out:", session.userId); // Cleanup session-specific data }, // Organization events onOrganizationCreated: async (org) => { console.log("Organization created:", org.id); // Initialize organization settings }, }, // =========================================== // CUSTOM HANDLERS (Optional) // =========================================== /* onRequest: (request) => { // Custom request handling console.log("Auth request:", request.url); }, onResponse: (response) => { // Custom response handling console.log("Auth response:", response.status); }, onError: (error, context) => { // Custom error handling console.error("Auth error:", error, context); // Send to monitoring service }, */ }); // =========================================== // TYPE EXPORTS FOR TYPE SAFETY // =========================================== export type Session = typeof auth.$Infer.Session; export type User = typeof auth.$Infer.User; export type Organization = typeof auth.$Infer.Organization;
Template 2: React Auth Client v2
File:
.claude/skills/better-auth-v2/client/react-auth-client-v2.template.tsx
/** * React Auth Client v2 with Enhanced Features * * Features: * - TypeScript integration * - Organization management * - 2FA support * - Passkey authentication * - Automatic session refresh * - Error boundaries * * Setup: * 1. Copy to `lib/auth-client.ts` * 2. Configure environment variables * 3. Use hooks in components */ import React, { createContext, useContext, useEffect, useState } from "react"; import { createAuthClient } from "better-auth/react"; import { organizationClient, twoFactorClient, passkeyClient } from "better-auth/client"; // =========================================== // AUTH CLIENT CONFIGURATION // =========================================== export const authClient = createAuthClient({ baseURL: process.env.NEXT_PUBLIC_AUTH_URL || "http://localhost:3000", // Fetch options fetchOptions: { credentials: "include", headers: { "Content-Type": "application/json", }, }, // Enable client plugins plugins: [ organizationClient({ organizationInvitationPage: "/invite", }), twoFactorClient({ twoFactorPage: "/2fa", }), passkeyClient(), ], // Storage configuration storage: { // Use localStorage for persistence mode: "localStorage", }, // Session configuration session: { // Auto-refresh session autoRefresh: true, // Refresh threshold (5 minutes before expiry) refreshThreshold: 5 * 60, }, }); // =========================================== // EXPORT AUTH METHODS // =========================================== export const { // Authentication useSession, signIn, signUp, signOut, // User management useUser, updateUser, // Password management forgetPassword, resetPassword, changePassword, // Email verification sendVerificationEmail, verifyEmail, // Organizations useActiveOrganization, useOrganizations, createOrganization, updateOrganization, deleteOrganization, inviteMember, removeMember, updateMemberRole, // 2FA enableTwoFactor, disableTwoFactor, verifyTwoFactor, generateBackupCodes, // Passkeys createPasskey, deletePasskey, usePasskeys, // OAuth listConnectedAccounts, unlinkAccount, } = authClient; // =========================================== // REACT CONTEXT PROVIDER // =========================================== interface AuthContextType { session: Session | null; user: User | null; isLoading: boolean; error: string | null; // Actions signIn: typeof signIn; signUp: typeof signUp; signOut: typeof signOut; refresh: () => Promise<void>; } const AuthContext = createContext<AuthContextType | undefined>(undefined); export function AuthProvider({ children }: { children: React.ReactNode }) { const { data: session, isPending, error } = useSession(); const [refreshing, setRefreshing] = useState(false); // Session refresh handler const refresh = React.useCallback(async () => { if (refreshing) return; setRefreshing(true); try { await authClient.getSession({ fetchOptions: { headers: { "Cache-Control": "no-cache" }, }, }); } catch (err) { console.error("Failed to refresh session:", err); } finally { setRefreshing(false); } }, [refreshing]); // Auto-refresh on visibility change useEffect(() => { const handleVisibilityChange = () => { if (!document.hidden && session) { refresh(); } }; document.addEventListener("visibilitychange", handleVisibilityChange); return () => document.removeEventListener("visibilitychange", handleVisibilityChange); }, [session, refresh]); // Periodic session refresh useEffect(() => { if (!session) return; const interval = setInterval(refresh, 5 * 60 * 1000); // 5 minutes return () => clearInterval(interval); }, [session, refresh]); const contextValue: AuthContextType = { session, user: session?.user || null, isLoading: isPending || refreshing, error: error?.message || null, signIn, signUp, signOut, refresh, }; return ( <AuthContext.Provider value={contextValue}> {children} </AuthContext.Provider> ); } // =========================================== // CUSTOM HOOKS // =========================================== /** * Use auth context */ export function useAuth() { const context = useContext(AuthContext); if (!context) { throw new Error("useAuth must be used within AuthProvider"); } return context; } /** * Check if user is authenticated */ export function useIsAuthenticated() { const { session, isLoading } = useAuth(); return { isAuthenticated: !!session, isLoading }; } /** * Require authentication (with redirect) */ export function useRequireAuth(redirectTo: string = "/signin") { const { isAuthenticated, isLoading } = useIsAuthenticated(); const router = useRouter(); useEffect(() => { if (!isLoading && !isAuthenticated) { router.push(redirectTo); } }, [isAuthenticated, isLoading, router, redirectTo]); return { isAuthenticated, isLoading }; } /** * Check user permissions */ export function usePermissions(permissions: string | string[]) { const { session } = useAuth(); const [userPermissions, setUserPermissions] = useState<string[]>([]); useEffect(() => { if (!session?.user) { setUserPermissions([]); return; } // Get user permissions based on role const getPermissions = async () => { try { const response = await fetch("/api/auth/permissions", { credentials: "include", }); const data = await response.json(); setUserPermissions(data.permissions || []); } catch (err) { console.error("Failed to fetch permissions:", err); setUserPermissions([]); } }; getPermissions(); }, [session]); const requiredPermissions = Array.isArray(permissions) ? permissions : [permissions]; const hasPermissions = requiredPermissions.every(p => userPermissions.includes(p) || userPermissions.includes("*") ); return { hasPermissions, permissions: userPermissions }; } /** * Require specific permissions */ export function useRequirePermissions( permissions: string | string[], redirectTo: string = "/unauthorized" ) { const { hasPermissions, isLoading } = usePermissions(permissions); const router = useRouter(); useEffect(() => { if (!isLoading && !hasPermissions) { router.push(redirectTo); } }, [hasPermissions, isLoading, router, redirectTo]); return { hasPermissions, isLoading }; } /** * Organization management */ export function useOrganization(orgId?: string) { const [organization, setOrganization] = useState<Organization | null>(null); const [members, setMembers] = useState<Member[]>([]); const [isLoading, setIsLoading] = useState(true); useEffect(() => { const fetchOrganization = async () => { try { const [orgData, membersData] = await Promise.all([ fetch(`/api/organizations/${orgId || "current"}`, { credentials: "include", }), fetch(`/api/organizations/${orgId || "current"}/members`, { credentials: "include", }), ]); const org = await orgData.json(); const memberList = await membersData.json(); setOrganization(org); setMembers(memberList); } catch (err) { console.error("Failed to fetch organization:", err); } finally { setIsLoading(false); } }; if (orgId || organization) { fetchOrganization(); } }, [orgId, organization]); return { organization, members, isLoading }; } /** * 2FA status */ export function useTwoFactor() { const [isEnabled, setIsEnabled] = useState(false); const [isVerified, setIsVerified] = useState(false); const [backupCodes, setBackupCodes] = useState<string[]>([]); useEffect(() => { const fetch2FAStatus = async () => { try { const response = await fetch("/api/auth/2fa/status", { credentials: "include", }); const data = await response.json(); setIsEnabled(data.enabled); setIsVerified(data.verified); } catch (err) { console.error("Failed to fetch 2FA status:", err); } }; fetch2FAStatus(); }, []); const enable = async () => { const result = await enableTwoFactor(); if (result.data) { setIsEnabled(true); if (result.data.backupCodes) { setBackupCodes(result.data.backupCodes); } } return result; }; const disable = async () => { const result = await disableTwoFactor(); if (result.data) { setIsEnabled(false); setIsVerified(false); setBackupCodes([]); } return result; }; return { isEnabled, isVerified, backupCodes, enable, disable, generateBackupCodes, }; } /** * Passkey management */ export function usePasskeyAuth() { const { data: passkeys, isLoading } = usePasskeys(); const [isSupported, setIsSupported] = useState(false); useEffect(() => { // Check if WebAuthn is supported setIsSupported(!!window.PublicKeyCredential); }, []); const addPasskey = async (name: string) => { return await createPasskey({ name, options: { authenticatorSelection: { userVerification: "required", residentKey: "preferred", }, }, }); }; const removePasskey = async (passkeyId: string) => { return await deletePasskey(passkeyId); }; return { passkeys: passkeys || [], isLoading, isSupported, addPasskey, removePasskey, }; } // =========================================== // UTILITY FUNCTIONS // =========================================== /** * Format user display name */ export function formatUserName(user: User): string { if (user.name) return user.name; if (user.displayName) return user.displayName; return user.email.split("@")[0]; } /** * Get user initials */ export function getUserInitials(user: User): string { const name = formatUserName(user); return name .split(" ") .map(word => word[0]) .join("") .toUpperCase() .slice(0, 2); } /** * Validate session activity */ export function isSessionActive(session: Session | null): boolean { if (!session) return false; const now = new Date(); const expires = new Date(session.expiresAt); // Check if session expires within 5 minutes const fiveMinutesFromNow = new Date(now.getTime() + 5 * 60 * 1000); return expires > fiveMinutesFromNow; } // =========================================== // TYPE EXPORTS // =========================================== export type Session = Awaited<ReturnType<typeof useSession>>["data"]; export type User = NonNullable<Session>["user"]; export type Organization = Awaited<ReturnType<typeof useOrganizations>>["data"]?.[0]; export type Member = Awaited<ReturnType<typeof removeMember>>["data"];
Getting Started
-
Install Dependencies
npm install better-auth@latest @better-auth/prisma-adapter -
Set Up Database
- Choose your adapter (Prisma, Drizzle, Kysely, or MongoDB)
- Copy and run the appropriate schema template
- Run migrations
-
Configure Auth
- Copy the production configuration template
- Set up environment variables
- Configure OAuth providers
-
Set Up Client
- Copy the React client template
- Wrap your app with
AuthProvider - Use hooks in components
-
Add Components
- Copy authentication component templates
- Customize for your application
- Add to your pages
Environment Variables
Create a
.env.local file with:
# Core Auth BETTER_AUTH_SECRET=your-secret-key-here BETTER_AUTH_URL=http://localhost:3000 # Database DATABASE_URL=your-database-url # OAuth Providers GITHUB_CLIENT_ID=your-github-client-id GITHUB_CLIENT_SECRET=your-github-client-secret GOOGLE_CLIENT_ID=your-google-client-id GOOGLE_CLIENT_SECRET=your-google-client-secret DISCORD_CLIENT_ID=your-discord-client-id DISCORD_CLIENT_SECRET=your-discord-client-secret MICROSOFT_CLIENT_ID=your-microsoft-client-id MICROSOFT_CLIENT_SECRET=your-microsoft-client-secret # Email Service SMTP_HOST=smtp.example.com SMTP_PORT=587 SMTP_SECURE=false SMTP_USER=your-smtp-user SMTP_PASSWORD=your-smtp-password EMAIL_FROM_ADDRESS=noreply@example.com EMAIL_FROM_NAME=Your App # App Settings APP_NAME=Your App Name PASSKEY_RP_ID=yourdomain.com PASSKEY_ORIGIN=https://yourdomain.com
Security Checklist
- Set strong, unique
BETTER_AUTH_SECRET - Enable OAuth token encryption
- Configure rate limiting
- Set up proper CORS origins
- Enable 2FA for sensitive roles
- Configure email verification
- Set up audit logging
- Review session settings
- Test password reset flow
- Verify OAuth callback URLs
⚠️ Critical Implementation Lessons Learned
Based on real-world OAuth implementation challenges, avoid these common pitfalls:
1. Database Schema Conflicts
Problem: Using reserved attribute names like
metadata in SQLAlchemy models
# ❌ Causes error class ChatMessage(Base): metadata = Column(JSON, nullable=True) # SQLAlchemy reserves this! # ✅ Fixed version class ChatMessage(Base): message_metadata = Column(JSON, nullable=True) # Use different name
2. OAuth Redirect URI Configuration
Critical Issues:
- Always include the full path with trailing slash removal
- For cross-platform deployments (GitHub Pages + HuggingFace):
# OAuth callback to backend AUTH_REDIRECT_URI=https://your-hf-space.hf.space/backend/auth/google/callback # Frontend redirect after auth FRONTEND_URL=https://your-username.github.io/your-repo
3. Session Middleware Requirements
Must-Have: OAuth requires SessionMiddleware for state parameter
from starlette.middleware.sessions import SessionMiddleware app.add_middleware( SessionMiddleware, secret_key=settings.jwt_secret_key, session_cookie="session_id", max_age=3600, same_site="lax", https_only=False, # Set true in production )
4. Docusaurus Base Path Handling
Issue: Static sites have base paths that affect OAuth callbacks
// Docusaurus config baseUrl: '/ai-humanoid-robotics/', // This affects ALL routes // Must match in environment FRONTEND_URL=https://username.github.io/ai-humanoid-robotics
5. Google OAuth Response Structure
Problem: Provider account ID (
sub) field might be missing
def create_or_update_account(db: Session, user: User, provider: str, account_info: dict) -> Account: provider_account_id = account_info.get('sub') # Primary: Google ID if not provider_account_id: # Fallback to email provider_account_id = account_info.get('email') if not provider_account_id: # Final fallback provider_account_id = str(user.id)
6. GitHub Pages Static Route Handling
Problem: Cannot create dynamic routes on static sites
// Create static page: src/pages/auth/callback.tsx export default function AuthCallbackPage() { return <OAuthCallbackHandler />; }
7. Dependency Management
Missing Dependencies Cause Failures:
# pyproject.toml dependencies = [ "sqlalchemy>=2.0.0", "alembic>=1.12.0", "python-jose[cryptography]>=3.3.0", "authlib>=1.2.1", "itsdangerous>=2.1.0", # Required for SessionMiddleware ]
8. CORS Configuration Checklist
Essential for Cross-Platform:
# main.py ALLOWED_ORIGINS = [ "http://localhost:3000", "https://username.github.io", "https://huggingface.co", "https://your-hf-space.hf.space", ]
9. JWT Secret Key Requirements
Must Configure:
class Settings(BaseSettings): # Add to your settings class jwt_secret_key: str = "your-super-secret-jwt-key-at-least-32-characters-long"
10. Production Deployment Checklist
- Test OAuth in production environment (localhost may work with wrong URLs)
- Verify redirect URIs match EXACTLY in provider console
- Include base path in frontend URLs for static sites
- Set up proper session middleware before OAuth routes
- Configure CORS for all deployment domains
- Enable HTTPS in production
- Set proper cookie attributes (SameSite, Secure, HttpOnly)
Next Steps
- Customize the templates for your application
- Set up additional OAuth providers as needed
- Configure advanced RBAC rules
- Set up monitoring and analytics
- Add custom email templates
- Configure webhooks for your business logic
- Review the lessons learned above to avoid common pitfalls
For detailed documentation and advanced configurations, visit the Better Auth documentation.