.claude/skills/nextauth/SKILL.md
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.
npx skillsauth add ftc8569/ftcmetrics nextauthInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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.
bun add next-auth@beta @auth/prisma-adapter
Create src/lib/auth.ts:
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",
},
});
Create src/app/api/auth/[...nextauth]/route.ts:
import { handlers } from "@/lib/auth";
export const { GET, POST } = handlers;
Create src/components/providers.tsx:
"use client";
import { SessionProvider } from "next-auth/react";
export function Providers({ children }: { children: React.ReactNode }) {
return <SessionProvider>{children}</SessionProvider>;
}
Wrap your app in src/app/layout.tsx:
import { Providers } from "@/components/providers";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}
Required models for NextAuth with database sessions:
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")
}
Add to .env:
# 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:
openssl rand -base64 32
Use the auth() function directly in Server Components:
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 <div>Welcome, {session.user.name}</div>;
}
Protect entire route groups with layout-level auth:
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}</>;
}
Use the useSession hook in Client Components:
"use client";
import { useSession, signOut } from "next-auth/react";
export function UserMenu() {
const { data: session, status } = useSession();
if (status === "loading") {
return <div>Loading...</div>;
}
if (!session?.user) {
return <a href="/login">Sign in</a>;
}
return (
<div>
<img src={session.user.image} alt={session.user.name} />
<span>{session.user.name}</span>
<button onClick={() => signOut({ callbackUrl: "/" })}>
Sign out
</button>
</div>
);
}
"use client";
import { signIn } from "next-auth/react";
export function LoginButtons() {
return (
<div>
<button onClick={() => signIn("google", { callbackUrl: "/dashboard" })}>
Continue with Google
</button>
<button onClick={() => signIn("discord", { callbackUrl: "/dashboard" })}>
Continue with Discord
</button>
<button onClick={() => signIn("github", { callbackUrl: "/dashboard" })}>
Continue with GitHub
</button>
</div>
);
}
The session callback extends the session with the database user ID:
callbacks: {
async session({ session, user }) {
if (session.user) {
session.user.id = user.id;
}
return session;
},
},
Access the user ID in components:
// Server Component
const session = await auth();
const userId = session?.user?.id;
// Client Component
const { data: session } = useSession();
const userId = session?.user?.id;
http://localhost:3000/api/auth/callback/googlehttp://localhost:3000/api/auth/callback/discordhttp://localhost:3000/api/auth/callback/githubSessionProvider wraps your entire app in the root layoutapp/api/auth/[...nextauth]/route.tsuser.iduser parameter, not tokenonDelete: Cascade on relations to handle user deletionprisma migrate dev after schema changes/api/auth/callback/[provider]openssl rand -base64 32Extend session types in src/types/next-auth.d.ts:
import { DefaultSession } from "next-auth";
declare module "next-auth" {
interface Session {
user: {
id: string;
} & DefaultSession["user"];
}
}
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
development
Configure TypeScript in a Bun/npm workspaces monorepo with shared base config, package-specific overrides, path aliases, and cross-package type sharing. Use when setting up tsconfig files, configuring path aliases, resolving module errors, or sharing types between packages.
development
Tailwind CSS styling for FTC Metrics with official FIRST colors and component patterns. Use when styling React components, creating responsive layouts, or implementing dark mode.
development
Configure and integrate Soketi WebSocket server with FTC Metrics for real-time updates. Use when setting up WebSocket connections, broadcasting scouting data changes, implementing presence channels for team collaboration, or debugging real-time features.
development
Create, structure, and optimize skills for the FTC Metrics project. Use when creating a new skill, improving an existing skill, or needing guidance on skill design patterns, triggers, frontmatter, and progressive disclosure.