--- name: nextauth description: >- Configure NextAuth.js v5 authentication with OAuth providers and Prisma adapter. Use when setting up login/logout, protecting routes, accessing sessions in components, adding OAuth providers, or troubleshooting authentication issues. license: MIT compatibility: [Claude Code] metadata: author: ftcmetrics version: "1.0.0" category: auth --- # NextAuth.js v5 Authentication Guide NextAuth.js v5 (Auth.js) is the authentication library for FTC Metrics. It uses OAuth providers (Google, Discord, GitHub) with a Prisma database adapter for session persistence. ## Quick Start ### 1. Install Dependencies ```bash bun add next-auth@beta @auth/prisma-adapter ``` ### 2. Create Auth Configuration Create `src/lib/auth.ts`: ```typescript import NextAuth from "next-auth"; import { PrismaAdapter } from "@auth/prisma-adapter"; import Google from "next-auth/providers/google"; import Discord from "next-auth/providers/discord"; import GitHub from "next-auth/providers/github"; import { prisma } from "@ftcmetrics/db"; export const { handlers, auth, signIn, signOut } = NextAuth({ adapter: PrismaAdapter(prisma), providers: [ Google({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, }), Discord({ clientId: process.env.DISCORD_CLIENT_ID!, clientSecret: process.env.DISCORD_CLIENT_SECRET!, }), GitHub({ clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET!, }), ], pages: { signIn: "/login", error: "/login", }, callbacks: { async session({ session, user }) { if (session.user) { session.user.id = user.id; } return session; }, }, session: { strategy: "database", }, }); ``` ### 3. Create API Route Handler Create `src/app/api/auth/[...nextauth]/route.ts`: ```typescript import { handlers } from "@/lib/auth"; export const { GET, POST } = handlers; ``` ### 4. Add Session Provider Create `src/components/providers.tsx`: ```typescript "use client"; import { SessionProvider } from "next-auth/react"; export function Providers({ children }: { children: React.ReactNode }) { return {children}; } ``` Wrap your app in `src/app/layout.tsx`: ```tsx import { Providers } from "@/components/providers"; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` ## Prisma Schema Requirements Required models for NextAuth with database sessions: ```prisma model User { id String @id @default(cuid()) name String? email String? @unique emailVerified DateTime? @map("email_verified") image String? createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") accounts Account[] sessions Session[] @@map("users") } model Account { id String @id @default(cuid()) userId String @map("user_id") type String provider String providerAccountId String @map("provider_account_id") refresh_token String? @db.Text access_token String? @db.Text expires_at Int? token_type String? scope String? id_token String? @db.Text session_state String? user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([provider, providerAccountId]) @@map("accounts") } model Session { id String @id @default(cuid()) sessionToken String @unique @map("session_token") userId String @map("user_id") expires DateTime user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@map("sessions") } ``` ## Environment Variables Add to `.env`: ```bash # NextAuth AUTH_SECRET="generate-with-openssl-rand-base64-32" # Google OAuth GOOGLE_CLIENT_ID="your-google-client-id" GOOGLE_CLIENT_SECRET="your-google-client-secret" # Discord OAuth DISCORD_CLIENT_ID="your-discord-client-id" DISCORD_CLIENT_SECRET="your-discord-client-secret" # GitHub OAuth GITHUB_CLIENT_ID="your-github-client-id" GITHUB_CLIENT_SECRET="your-github-client-secret" ``` Generate AUTH_SECRET: ```bash openssl rand -base64 32 ``` ## Session Management Patterns ### Server Components (Recommended) Use the `auth()` function directly in Server Components: ```tsx import { auth } from "@/lib/auth"; import { redirect } from "next/navigation"; export default async function DashboardPage() { const session = await auth(); if (!session?.user) { redirect("/login"); } return
Welcome, {session.user.name}
; } ``` ### Protected Layouts Protect entire route groups with layout-level auth: ```tsx import { auth } from "@/lib/auth"; import { redirect } from "next/navigation"; export default async function ProtectedLayout({ children, }: { children: React.ReactNode; }) { const session = await auth(); if (!session?.user) { redirect("/login"); } return <>{children}; } ``` ### Client Components Use the `useSession` hook in Client Components: ```tsx "use client"; import { useSession, signOut } from "next-auth/react"; export function UserMenu() { const { data: session, status } = useSession(); if (status === "loading") { return
Loading...
; } if (!session?.user) { return Sign in; } return (
{session.user.name} {session.user.name}
); } ``` ### Sign In with Providers ```tsx "use client"; import { signIn } from "next-auth/react"; export function LoginButtons() { return (
); } ``` ## Adding User ID to Session The session callback extends the session with the database user ID: ```typescript callbacks: { async session({ session, user }) { if (session.user) { session.user.id = user.id; } return session; }, }, ``` Access the user ID in components: ```tsx // Server Component const session = await auth(); const userId = session?.user?.id; // Client Component const { data: session } = useSession(); const userId = session?.user?.id; ``` ## OAuth Provider Setup ### Google 1. Go to [Google Cloud Console](https://console.cloud.google.com) 2. Create OAuth 2.0 credentials 3. Add authorized redirect URI: `http://localhost:3000/api/auth/callback/google` ### Discord 1. Go to [Discord Developer Portal](https://discord.com/developers/applications) 2. Create application and OAuth2 credentials 3. Add redirect URI: `http://localhost:3000/api/auth/callback/discord` ### GitHub 1. Go to [GitHub Developer Settings](https://github.com/settings/developers) 2. Create OAuth App 3. Add callback URL: `http://localhost:3000/api/auth/callback/github` ## Common Pitfalls ### Session Not Available - Ensure `SessionProvider` wraps your entire app in the root layout - Ensure the API route exists at `app/api/auth/[...nextauth]/route.ts` ### User ID Missing from Session - Add the session callback to include `user.id` - When using database strategy, user info comes from the `user` parameter, not `token` ### Database Session Issues - Ensure Prisma schema matches NextAuth requirements exactly - Use `onDelete: Cascade` on relations to handle user deletion - Run `prisma migrate dev` after schema changes ### OAuth Callback Errors - Verify redirect URIs match exactly in provider console - Include the full path: `/api/auth/callback/[provider]` - For production, update URIs to use HTTPS and your domain ### AUTH_SECRET Missing - Generate with `openssl rand -base64 32` - Required in production, auto-generated in development ## TypeScript Type Extensions Extend session types in `src/types/next-auth.d.ts`: ```typescript import { DefaultSession } from "next-auth"; declare module "next-auth" { interface Session { user: { id: string; } & DefaultSession["user"]; } } ``` ## File Structure Reference ``` packages/web/src/ lib/ auth.ts # NextAuth configuration components/ providers.tsx # SessionProvider wrapper app/ api/auth/[...nextauth]/ route.ts # API route handler layout.tsx # Root layout with Providers login/ page.tsx # Login page (server) login-form.tsx # OAuth buttons (client) dashboard/ layout.tsx # Protected layout page.tsx # Uses auth() for session ``` ## References - [NextAuth.js v5 Documentation](https://authjs.dev) - [Prisma Adapter](https://authjs.dev/getting-started/adapters/prisma) - [OAuth Providers](https://authjs.dev/getting-started/providers)