Claude Code Plugins

Community-maintained marketplace

Feedback

Production-ready authentication system using Better Auth v2 with latest features. Includes OAuth providers, advanced RBAC, multi-tenant support, and security best practices.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name better-auth-v2
description Production-ready authentication system using Better Auth v2 with latest features. Includes OAuth providers, advanced RBAC, multi-tenant support, and security best practices.
category authentication
version 2.0.0
dependencies [object Object], [object Object], [object Object]

Better 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 templatesEnhanced database schemas (Prisma, Drizzle, Kysely, MongoDB) ✅ Modern React/Vue/Svelte authentication clientsMiddleware for route protection with advanced featuresFine-grained RBAC and permission utilitiesOAuth provider setup with token encryption2FA and passkey authentication templatesAdvanced security configurationsSeparate 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)

  1. 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.)
  2. 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
  3. 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
  4. Better-Auth Core Implementation

    • Install better-auth + better-auth-ui + required plugins (google, apple, facebook, twitter)
    • Configure env variables for all OAuth providers
    • Set up email/password + magic links as fallback
    • Add user metadata table/extension for background questionnaire answers
  5. 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 user_metadata table for future personalization
  6. UI Integration (Better-Auth-UI)

    • Create /sign-in, /sign-up pages with <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
  7. Session Management & Protection

    • Server-side session validation
    • authClient React hook for frontend state
    • 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
  • .env.example with all required variables
  • 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)
  • /src/auth folder structure with full Better-Auth config
  • All OAuth callbacks configured
  • /app/(auth)/sign-in/[[...better-auth]]/page.tsx using <AuthUI />
  • Header with dynamic auth state
  • /app/onboarding/page.tsx with questionnaire
  • 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

  1. 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
  2. Enterprise Auth Config (config/enterprise-auth.template.ts)

    • Multi-tenant with organizations
    • Custom roles and permissions
    • 2FA enforcement options
    • Audit logging ready
  3. OAuth-First Config (config/oauth-first.template.ts)

    • OAuth providers as primary auth
    • Account linking across providers
    • Encrypted token storage
    • Social login preferences
  4. B2B SaaS Config (config/b2b-saas.template.ts)

    • Organization-based auth
    • Member invitations
    • Role hierarchy management
    • Team collaboration features

Database Schema Templates

  1. Prisma Schema v2 (schemas/prisma-v2.template.prisma)
  2. Drizzle Schema v2 (schemas/drizzle-v2.template.ts)
  3. Kysely Schema v2 (schemas/kysely-v2.template.ts)
  4. MongoDB Schema (schemas/mongodb.template.ts)

Client-Side Templates

  1. React Auth Client v2 (client/react-auth-client-v2.template.tsx)
  2. Vue 3 Auth Client (client/vue3-auth-client.template.ts)
  3. Svelte Auth Client (client/svelte-auth-client.template.ts)
  4. TypeScript Auth Utilities (client/auth-utils.template.ts)

Component Templates

  1. Modern Sign In Form (components/SignInForm-v2.template.tsx)
  2. Multi-Step Sign Up (components/SignUpFlow.template.tsx)
  3. OAuth Provider Grid (components/OAuthProviderGrid.template.tsx)
  4. 2FA Setup Component (components/TwoFactorSetup.template.tsx)
  5. Organization Manager (components/OrganizationManager.template.tsx)
  6. Passkey Enrollment (components/PasskeyEnroll.template.tsx)
  7. Session Security (components/SessionSecurity.template.tsx)

Middleware Templates

  1. Next.js App Router Middleware (middleware/nextjs-app-router.template.ts)
  2. Advanced RBAC Middleware (middleware/rbac-gateway.template.ts)
  3. Rate Limiting Middleware (middleware/rate-limiter.template.ts)
  4. Audit Logging Middleware (middleware/audit-logger.template.ts)

Utility Templates

  1. Permission Engine (utils/permission-engine.template.ts)
  2. Role Manager (utils/role-manager.template.ts)
  3. Security Validator (utils/security-validator.template.ts)
  4. 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

  1. Install Dependencies

    npm install better-auth@latest @better-auth/prisma-adapter
    
  2. Set Up Database

    • Choose your adapter (Prisma, Drizzle, Kysely, or MongoDB)
    • Copy and run the appropriate schema template
    • Run migrations
  3. Configure Auth

    • Copy the production configuration template
    • Set up environment variables
    • Configure OAuth providers
  4. Set Up Client

    • Copy the React client template
    • Wrap your app with AuthProvider
    • Use hooks in components
  5. 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

  1. Customize the templates for your application
  2. Set up additional OAuth providers as needed
  3. Configure advanced RBAC rules
  4. Set up monitoring and analytics
  5. Add custom email templates
  6. Configure webhooks for your business logic
  7. Review the lessons learned above to avoid common pitfalls

For detailed documentation and advanced configurations, visit the Better Auth documentation.