nextjs-authentication — Claude Code skill: Auth.js 5 in Next.js
A skill with ready authentication patterns for Next.js 15+ App Router on Auth.js 5: providers, route protection, sessions and roles.
npx skills add giuseppe-trisciuoglio/developer-kit --skill nextjs-authentication -a claude-codeWhat nextjs-authentication does
The skill walks through the whole authentication path in Next.js 15+ App Router on Auth.js 5. Ten steps sit inside: installing next-auth@beta, environment variables in .env.local, an auth.ts file with GitHub and Google providers, the app/api/auth/[...nextauth]/route.ts route, middleware.ts for route protection, session access in Server Components and Server Actions, sign-in and sign-out, roles, and TypeScript type extensions.
You need AUTH_SECRET — the skill suggests generating it with openssl rand -base64 32. OAuth providers want their own GITHUB_ID, GITHUB_SECRET, GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET. Without those the provider simply will not come up, so register the apps in the provider consoles first.
Route protection relies on middleware.ts with a matcher that skips static assets and images. In Server Actions the skill insists on checking the session before any data mutation — otherwise the action runs for anyone who posts the form. Roles are read from session.user.role, and types for Session, User and JWT go into types/next-auth.d.ts.
What it helps with
- wire up GitHub and Google sign-in in Next.js 15
- lock
/dashboardbehind middleware for guests - verify the session in a Server Action before writing to the database
- split access between user and admin roles
- add TypeScript types for session and JWT
Example prompts for the agent
- Set up authentication in a Next.js 15 App Router project with Auth.js 5 and GitHub plus Google providers
- Add middleware that redirects unauthenticated visitors from /dashboard to /login
- Check for the admin role in a Server Component and call unauthorized for everyone else
How to install nextjs-authentication
1Skills CLI (Vercel)
npx skills add giuseppe-trisciuoglio/developer-kit --skill nextjs-authentication -a claude-code2Manually: put the files in place
~/.claude/skills/nextjs-authentication/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/nextjs-authentication/
1Skills CLI (Vercel)
npx skills add giuseppe-trisciuoglio/developer-kit --skill nextjs-authentication -a codex2Manually: put the files in place
.agents/skills/nextjs-authentication/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add giuseppe-trisciuoglio/developer-kit --skill nextjs-authentication -a cursor2Manually: put the files in place
.cursor/skills/nextjs-authentication/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install giuseppe-trisciuoglio/developer-kit nextjs-authentication2Manually: put the files in place
.github/skills/nextjs-authentication/SKILL.md1Skills CLI (Vercel)
npx skills add giuseppe-trisciuoglio/developer-kit --skill nextjs-authentication -a gemini-cli2Platform CLI
gemini skills install https://github.com/giuseppe-trisciuoglio/developer-kit --path plugins/developer-kit-typescript/skills/nextjs-authenticationManually: put the files in place
.devin/skills/nextjs-authentication/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add giuseppe-trisciuoglio/developer-kit --skill nextjs-authentication -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about nextjs-authentication
How do I install nextjs-authentication in Claude?
Run npx skills add giuseppe-trisciuoglio/developer-kit --skill nextjs-authentication -a claude-code. The skill file lands at ~/.claude/skills/nextjs-authentication/SKILL.md.
What does nextjs-authentication do?
It provides authentication patterns for Next.js 15+ App Router on Auth.js 5: provider configuration, route protection, session handling in Server Components and Server Actions, roles and types.
What do I need before using the skill?
AUTH_SECRET and AUTH_URL in .env.local, plus provider client keys such as GITHUB_ID and GITHUB_SECRET for OAuth. The skill suggests generating the secret with openssl rand -base64 32.
Source text
SKILL.md · MIT
---
name: nextjs-authentication
description: Provides authentication implementation patterns for Next.js 15+ App Router using Auth.js 5 (NextAuth.js). Use when setting up authentication flows, implementing protected routes, managing sessions in Server Components and Server Actions, configuring OAuth providers, implementing role-based access control, or handling sign-in/sign-out flows in Next.js applications.
allowed-tools: Read, Write, Edit, Bash
---
# Next.js Authentication
## Overview
Provides authentication implementation patterns for Next.js 15+ App Router using Auth.js 5 (NextAuth.js), covering the complete authentication lifecycle from initial setup to production-ready role-based access control implementations.
## When to Use
- Setting up Auth.js 5 from scratch or adding OAuth providers
- Implementing protected routes with Middleware
- Handling authentication in Server Components and Server Actions
- Implementing role-based access control (RBAC)
- Creating credential-based or OAuth sign-in/sign-out flows
## Instructions
### 1. Install Dependencies
Install Auth.js v5 (beta) for Next.js App Router:
```bash
npm install next-auth@beta
```
### 2. Configure Environment Variables
Create `.env.local` with required variables:
```bash
# Required for Auth.js
AUTH_SECRET="your-secret-key-here"
AUTH_URL="http://localhost:3000"
# OAuth Providers (add as needed)
GITHUB_ID="your-github-client-id"
GITHUB_SECRET="your-github-client-secret"
GOOGLE_CLIENT_ID="your-google-client-id"
GOOGLE_CLIENT_SECRET="your-google-client-secret"
```
Generate `AUTH_SECRET` with:
```bash
openssl rand -base64 32
```
### 3. Create Auth Configuration
Create `auth.ts` in the project root with providers and callbacks:
```typescript
import NextAuth from "next-auth";
import GitHub from "next-auth/providers/github";
import Google from "next-auth/providers/google";
export const {
handlers: { GET, POST },
auth,
signIn,
signOut,
} = NextAuth({
providers: [
GitHub({
clientId: process.env.GITHUB_ID!,
clientSecret: process.env.GITHUB_SECRET!,
}),
Google({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
],
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id;
}
return token;
},
async session({ session, token }) {
if (token) {
session.user.id = token.id as string;
}
return session;
},
},
pages: {
signIn: "/login",
error: "/error",
},
});
```
### 4. Create API Route Handler
Create `app/api/auth/[...nextauth]/route.ts`:
```typescript
export { GET, POST } from "@/auth";
```
### 5. Add Middleware for Route Protection
Create `middleware.ts` in the project root:
```typescript
import { auth } from "@/auth";
import { NextResponse } from "next/server";
export default auth((req) => {
const { nextUrl } = req;
const isLoggedIn = !!req.auth;
const isApiAuthRoute = nextUrl.pathname.startsWith("/api/auth");
const isPublicRoute = ["/", "/login", "/register"].includes(nextUrl.pathname);
const isProtectedRoute = nextUrl.pathname.startsWith("/dashboard");
if (isApiAuthRoute) return NextResponse.next();
if (!isLoggedIn && isProtectedRoute) {
return NextResponse.redirect(new URL("/login", nextUrl));
}
if (isLoggedIn && nextUrl.pathname === "/login") {
return NextResponse.redirect(new URL("/dashboard", nextUrl));
}
return NextResponse.next();
});
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico|.*\\.png$).*)"],
};
```
### 6. Access Session in Server Components
Use the `auth()` function to access session in Server Components:
```tsx
import { auth } from "@/auth";
import { redirect } from "next/navigation";
export default async function DashboardPage() {
const session = await auth();
if (!session) {
redirect("/login");
}
return (
<div>
<h1>Welcome, {session.user.name}</h1>
</div>
);
}
```
### 7. Secure Server Actions
Always verify authentication in Server Actions before mutations:
```tsx
"use server";
import { auth } from "@/auth";
export async function createTodo(formData: FormData) {
const session = await auth();
if (!session?.user) {
throw new Error("Unauthorized");
}
// Proceed with protected action
const title = formData.get("title") as string;
await db.todo.create({
data: { title, userId: session.user.id },
});
}
```
### 8. Handle Sign-In/Sign-Out
Create a login page with server action:
```tsx
// app/login/page.tsx
import { signIn } from "@/auth";
import { redirect } from "next/navigation";
export default function LoginPage() {
async function handleLogin(formData: FormData) {
"use server";
const result = await signIn("credentials", {
email: formData.get("email"),
password: formData.get("password"),
redirect: false,
});
if (result?.error) {
return { error: "Invalid credentials" };
}
redirect("/dashboard");
}
return (
<form action={handleLogin}>
<input name="email" type="email" placeholder="Email" required />
<input name="password" type="password" placeholder="Password" required />
<button type="submit">Sign In</button>
</form>
);
}
```
For client-side sign-out:
```tsx
"use client";
import { signOut } from "next-auth/react";
export function SignOutButton() {
return <button onClick={() => signOut()}>Sign Out</button>;
}
```
### 9. Implement Role-Based Access
Check roles in Server Components:
```tsx
import { auth } from "@/auth";
import { unauthorized } from "next/navigation";
export default async function AdminPage() {
const session = await auth();
if (session?.user?.role !== "admin") {
unauthorized();
}
return <AdminDashboard />;
}
```
### 10. Extend TypeScript Types
Create `types/next-auth.d.ts` for type-safe sessions:
```typescript
import { DefaultSession } from "next-auth";
declare module "next-auth" {
interface Session {
user: {
id: string;
role: "user" | "admin";
} & DefaultSession["user"];
}
interface User {
role?: "user" | "admin";
}
}
declare module "next-auth/jwt" {
interface JWT {
id?: string;
role?: "user" | "admin";
}
}
```
## Examples
### Example 1: Complete Protected Dashboard
**Input:** User needs a dashboard accessible only to authenticated users
**Implementation:**
```tsx
// app/dashboard/page.tsx
import { auth } from "@/auth";
import { redirect } from "next/navigation";
import { getUserTodos } from "@/app/lib/data";
export default async function DashboardPage() {
const session = await auth();
if (!session?.user?.id) {
redirect("/login");
}
const todos = await getUserTodos(session.user.id);
return (
<main>
<h1>Welcome, {session.user.name}</h1>
<p>Email: {session.user.email}</p>
<TodoList todos={todos} />
</main>
);
}
```
**Output:** Dashboard renders only for authenticated users, with their specific data.
### Example 2: Role-Based Admin Panel
**Input:** Admin panel should be accessible only to users with "admin" role
**Implementation:**
```tsx
// app/admin/page.tsx
import { auth } from "@/auth";
import { unauthorized } from "next/navigation";
export default async function AdminPage() {
const session = await auth();
if (session?.user?.role !== "admin") {
unauthorized();
}
return (
<main>
<h1>Admin Panel</h1>
<p>Welcome, administrator {session.user.name}</p>
</main>
);
}
```
**Output:** Only admin users see the panel; others get 401 error.
### Example 3: Secure Server Action with Form
**Input:** Form submission should only work for authenticated users
**Implementation:**
```tsx
// app/components/create-todo-form.tsx
"use server";
import { auth } from "@/auth";
import { revalidatePath } from "next/cache";
export async function createTodo(formData: FormData) {
const session = await auth();
if (!session?.user?.id) {
throw new Error("Unauthorized");
}
const title = formData.get("title") as string;
await db.todo.create({
data: {
title,
userId: session.user.id,
},
});
revalidatePath("/dashboard");
}
// Usage in component
export function CreateTodoForm() {
return (
<form action={createTodo}>
<input name="title" placeholder="New todo..." required />
<button type="submit">Add Todo</button>
</form>
);
}
```
**Output:** Todo created only for authenticated user; unauthorized requests throw error.
## Best Practices
1. **Use Server Components by default** - Access session directly without client-side JavaScript
2. **Minimize Client Components** - Only use `useSession()` for reactive session updates
3. **Cache session checks** - Use React's `cache()` for repeated lookups in the same render
4. **Middleware for optimistic checks** - Redirect quickly, but always re-verify in Server Actions
5. **Treat Server Actions like API endpoints** - Always authenticate before mutations
6. **Never hardcode secrets** - Use environment variables for all credentials
7. **Implement proper error handling** - Return appropriate HTTP status codes
8. **Use TypeScript type extensions** - Extend NextAuth types for custom fields
9. **Separate auth logic** - Create a DAL (Data Access Layer) for consistent checks
10. **Test authentication flows** - Mock `auth()` function in unit tests
## Constraints and Warnings
### Critical Limitations
- **Middleware runs on Edge runtime** - Cannot use Node.js APIs like database drivers
- **Server Components cannot set cookies** - Use Server Actions for cookie operations
- **Session callback timing** - Only called on session creation/access, not every request
### Common Mistakes
```tsx
// ❌ WRONG: Setting cookies in Server Component
export default async function Page() {
cookies().set("key", "value"); // Won't work
}
// ✅ CORRECT: Use Server Action
async function setCookieAction() {
"use server";
cookies().set("key", "value");
}
```
```typescript
// ❌ WRONG: Database queries in Middleware
export default auth(async (req) => {
const user = await db.user.findUnique(); // Won't work in Edge
});
// ✅ CORRECT: Use only Edge-compatible APIs
export default auth(async (req) => {
const session = req.auth; // This works
});
```
### Security Considerations
- Always verify authentication in Server Actions - middleware alone is not enough
- Use `unauthorized()` for unauthenticated access, `redirect()` for other cases
- Store sensitive tokens in `httpOnly` cookies
- Validate all user input before processing
- Use HTTPS in production
- Set appropriate cookie `sameSite` attributes
## References
- [references/authjs-setup.md](references/authjs-setup.md) - Complete Auth.js 5 setup guide with Prisma/Drizzle adapters
- [references/oauth-providers.md](references/oauth-providers.md) - Provider-specific configurations (GitHub, Google, Discord, Auth0, etc.)
- [references/database-adapter.md](references/database-adapter.md) - Database session management with Prisma, Drizzle, and custom adapters
- [references/testing-patterns.md](references/testing-patterns.md) - Testing authentication flows with Vitest and Playwright
© giuseppe-trisciuoglio · MIT · giuseppe-trisciuoglio/developer-kit