Customisation
Common customisation tasks - adjusting route guards, adding OAuth providers, tweaking limits, using the role system, and theming.
Changing route protection
Edit the PUBLIC_ROUTES and AUTH_ROUTES arrays in proxy.ts:
// Unauthenticated users can access these routes const PUBLIC_ROUTES = [ '/', '/login', '/register', '/forgot-password', '/reset-password', '/verify-email', '/about', // add your public pages here '/pricing', ] // Authenticated users are redirected to /dashboard from these routes const AUTH_ROUTES = ['/login', '/register']
Adding auth providers
NextAuth v5 supports many OAuth providers. To add GitHub:
- Create a GitHub OAuth App at github.com/settings/developers
- Set the callback URL to: http://localhost:3000/api/auth/callback/github
- Add AUTH_GITHUB_ID and AUTH_GITHUB_SECRET to .env.local
import GitHub from 'next-auth/providers/github'
providers: [
Credentials({ ... }),
Google({ ... }),
GitHub({
clientId: process.env.AUTH_GITHUB_ID,
clientSecret: process.env.AUTH_GITHUB_SECRET,
}),
]The same JWT and session callbacks will attach id, role, and emailVerified to the token for all providers.
Changing OTP expiry
// Change this value - used everywhere OTPs are created export const OTP_EXPIRY_MINUTES = 10
This constant is imported by every action that creates an OTP. Changing it updates expiry across all flows - verification and password reset.
Changing rate limits
// Auth routes (login, register) - default: 5 per minute
export const authRatelimit = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(10, '1 m'), // 10 per minute
prefix: 'nextforge:auth',
})
// OTP routes (send, resend) - default: 3 per 10 minutes
export const otpRatelimit = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(2, '15 m'), // 2 per 15 minutes
prefix: 'nextforge:otp',
})Using roles
The role field is included on every user, propagated through the JWT, and available on the session. Default value: 'user'.
Checking roles in a server action:
export async function adminOnlyAction(): Promise<ActionState> {
const session = await auth()
if (!session) {
return { success: false, error: 'Not authenticated.' }
}
if (session.user.role !== 'admin') {
return { success: false, error: 'Forbidden.' }
}
// ... admin logic
}Checking roles in a server component:
import { auth } from '@/lib/auth'
import { redirect } from 'next/navigation'
export default async function AdminPage() {
const session = await auth()
if (!session || session.user.role !== 'admin') {
redirect('/')
}
return <AdminDashboard />
}To promote a user to admin, update the role field directly in your database. You can build an admin action for this or do it manually via your database dashboard.
Changing the theme
All colors are defined in the @themeblock in app/globals.css. Change the values to update the entire application's color palette:
@theme {
/* Change primary color - affects buttons, links, focus rings, active states */
--color-primary: #7c3aed; /* purple instead of blue */
--color-primary-hover: #6d28d9;
--color-primary-foreground: #ffffff;
/* Change background colors for a lighter theme */
--color-background: #ffffff;
--color-background-card: #f8fafc;
--color-foreground: #0f172a;
}No component files need to be touched - every component reads from these variables.
Adding new pages
To add a new protected page:
- Create app/your-page/page.tsx as a server component
- Add the session check at the top:
import { auth } from '@/lib/auth'
import { redirect } from 'next/navigation'
export default async function YourPage() {
const session = await auth()
if (!session) redirect('/login')
return (
<main>
<h1>Welcome, {session.user.name}</h1>
</main>
)
}- If the page should be inaccessible to logged-out users, add it to the protected path check in proxy.ts - or let the redirect() handle it (redirect() is sufficient; proxy.ts is optional for non-auth-route pages)