Skip to content

BetterAuth Advanced (80-20)#

Advanced patterns, hooks, and real-world usage.

Custom Hooks#

useSession Hook#

import { authClient } from '@/lib/auth-client'
import { useEffect, useState } from 'react'
import type { Session } from 'better-auth'

export function useSession() {
  const [session, setSession] = useState<Session | null>(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    authClient.getSession().then(({ data }) => {
      setSession(data)
      setLoading(false)
    })
  }, [])

  return { session, loading, user: session?.user }
}

// Usage
function Profile() {
  const { user, loading } = useSession()

  if (loading) return <div>Loading...</div>
  if (!user) return <div>Not logged in</div>

  return <div>Hello, {user.name}</div>
}

useAuth Hook (Complete)#

import { authClient } from '@/lib/auth-client'
import { useState, useEffect, useCallback } from 'react'

export function useAuth() {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(true)

  const fetchSession = useCallback(async () => {
    const { data } = await authClient.getSession()
    setUser(data?.user ?? null)
    setLoading(false)
  }, [])

  useEffect(() => {
    fetchSession()
  }, [fetchSession])

  const signIn = async (email: string, password: string) => {
    const { data, error } = await authClient.signIn.email({
      email,
      password,
    })
    if (data) {
      setUser(data.user)
    }
    return { data, error }
  }

  const signUp = async (email: string, password: string, name: string) => {
    const { data, error } = await authClient.signUp.email({
      email,
      password,
      name,
    })
    if (data) {
      setUser(data.user)
    }
    return { data, error }
  }

  const signOut = async () => {
    await authClient.signOut()
    setUser(null)
  }

  return {
    user,
    loading,
    isAuthenticated: !!user,
    signIn,
    signUp,
    signOut,
    refetch: fetchSession,
  }
}

Protected Route Component#

import { useSession } from '@/hooks/useSession'
import { useRouter } from 'next/navigation'
import { useEffect } from 'react'

export function ProtectedRoute({ children }: { children: React.ReactNode }) {
  const { user, loading } = useSession()
  const router = useRouter()

  useEffect(() => {
    if (!loading && !user) {
      router.push('/login')
    }
  }, [user, loading, router])

  if (loading) {
    return <div>Loading...</div>
  }

  if (!user) {
    return null
  }

  return <>{children}</>
}

// Usage
function DashboardPage() {
  return (
    <ProtectedRoute>
      <Dashboard />
    </ProtectedRoute>
  )
}

Role-Based Access Control#

// Server-side
import { auth } from '@/lib/auth'
import { headers } from 'next/headers'

export async function requireAdmin() {
  const session = await auth.api.getSession({
    headers: headers(),
  })

  if (!session?.user) {
    throw new Error('Unauthorized')
  }

  if (session.user.role !== 'admin') {
    throw new Error('Forbidden: Admin access required')
  }

  return session.user
}

// API Route
export async function DELETE(request: Request) {
  const user = await requireAdmin()

  // Admin-only logic here

  return Response.json({ success: true })
}

Multi-Provider Setup#

export const auth = betterAuth({
  database: {
    provider: 'pg',
    url: process.env.DATABASE_URL!,
  },

  emailAndPassword: {
    enabled: true,
    requireEmailVerification: true,
  },

  socialProviders: {
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    },
    github: {
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    },
    discord: {
      clientId: process.env.DISCORD_CLIENT_ID!,
      clientSecret: process.env.DISCORD_CLIENT_SECRET!,
    },
  },
})

// Client usage
await authClient.signIn.social({
  provider: 'google',
  callbackURL: '/dashboard',
})

await authClient.signIn.social({
  provider: 'github',
  callbackURL: '/dashboard',
})

Email Templates#

export const auth = betterAuth({
  // ... other config

  emailVerification: {
    sendVerificationEmail: async ({ user, token, url }) => {
      await sendEmail({
        to: user.email,
        subject: 'Verify your email',
        html: `
          <h1>Welcome ${user.name}!</h1>
          <p>Click the link below to verify your email:</p>
          <a href="${url}">Verify Email</a>
          <p>This link expires in 24 hours.</p>
        `,
      })
    },
  },

  passwordReset: {
    sendResetEmail: async ({ user, token, url }) => {
      await sendEmail({
        to: user.email,
        subject: 'Reset your password',
        html: `
          <h1>Password Reset</h1>
          <p>Click the link below to reset your password:</p>
          <a href="${url}">Reset Password</a>
          <p>If you didn't request this, you can ignore this email.</p>
        `,
      })
    },
  },
})

Two-Factor Authentication#

export const auth = betterAuth({
  // ... other config

  twoFactor: {
    enabled: true,
    issuer: 'YourApp',
  },
})

// Enable 2FA
const { data } = await authClient.twoFactor.enable()

// data.qrCode - show to user for scanning
// data.secret - backup codes

// Verify OTP
await authClient.twoFactor.verify({
  code: '123456',
})

// Sign in with 2FA
await authClient.signIn.email({
  email,
  password,
})

// Then verify OTP
await authClient.twoFactor.verifySignIn({
  code: '123456',
})

Session Management#

// Get all sessions
const sessions = await authClient.listSessions()

// Revoke specific session
await authClient.revokeSession({ sessionId: 'session-id' })

// Revoke all other sessions
await authClient.revokeOtherSessions()

// Update session
await authClient.updateSession({
  sessionId: 'session-id',
  data: {
    deviceName: 'iPhone 15',
  },
})

Custom User Fields#

// Extend user schema
export const auth = betterAuth({
  // ... other config

  user: {
    additionalFields: {
      phoneNumber: {
        type: 'string',
        required: false,
      },
      bio: {
        type: 'string',
        required: false,
      },
      avatar: {
        type: 'string',
        required: false,
      },
    },
  },
})

// Update custom fields
await authClient.updateUser({
  phoneNumber: '+1234567890',
  bio: 'Software developer',
  avatar: 'https://example.com/avatar.jpg',
})

Rate Limiting#

export const auth = betterAuth({
  // ... other config

  rateLimit: {
    window: 60, // 60 seconds
    max: 5, // 5 attempts
    storage: 'memory', // or 'redis'
  },
})

Hooks (Lifecycle Events)#

export const auth = betterAuth({
  // ... other config

  hooks: {
    after: {
      signUp: async ({ user }) => {
        // Send welcome email
        await sendWelcomeEmail(user.email)

        // Log event
        console.log('New user signed up:', user.email)
      },

      signIn: async ({ user }) => {
        // Update last login
        await db.user.update({
          where: { id: user.id },
          data: { lastLoginAt: new Date() },
        })
      },
    },
  },
})

API Error Handling#

const { data, error } = await authClient.signIn.email({
  email,
  password,
})

if (error) {
  switch (error.status) {
    case 400:
      toast.error('Invalid email or password')
      break
    case 429:
      toast.error('Too many attempts. Try again later.')
      break
    case 403:
      toast.error('Email not verified. Check your inbox.')
      break
    default:
      toast.error('Something went wrong. Please try again.')
  }
  return
}

// Success
router.push('/dashboard')

Remember Me#

await authClient.signIn.email({
  email,
  password,
  rememberMe: true, // Extends session duration
})
// Server config
export const auth = betterAuth({
  // ... other config

  magicLink: {
    enabled: true,
    sendMagicLink: async ({ email, url }) => {
      await sendEmail({
        to: email,
        subject: 'Your login link',
        html: `<a href="${url}">Click to login</a>`,
      })
    },
  },
})

// Client usage
await authClient.signIn.magicLink({
  email: 'user@example.com',
  callbackURL: '/dashboard',
})

Production Tips

  • Always use HTTPS in production
  • Set strong session secrets
  • Enable rate limiting
  • Implement CSRF protection
  • Log authentication events
  • Use environment variables for secrets