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
})
Magic Link Login#
// 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