auth-patterns
About
This skill provides guidance on implementing authentication and authorization in Next.js applications. It covers patterns like NextAuth.js (Auth.js), middleware protection, session management, and JWT strategies. Use it when developers ask about protected routes, login flows, or need help choosing between authentication libraries.
Quick Install
Claude Code
Recommendednpx skills add davepoon/buildwithclaude/plugin add https://github.com/davepoon/buildwithclaudegit clone https://github.com/davepoon/buildwithclaude.git ~/.claude/skills/auth-patternsCopy and paste this command in Claude Code to install this skill
Documentation
Authentication Patterns in Next.js
Overview
Next.js supports multiple authentication strategies. This skill covers common patterns including NextAuth.js (Auth.js), middleware-based protection, and session management.
Authentication Libraries
| Library | Best For |
|---|---|
| NextAuth.js (Auth.js) | Full-featured auth with providers |
| Clerk | Managed auth service |
| Lucia | Lightweight, flexible auth |
| Supabase Auth | Supabase ecosystem |
| Custom JWT | Full control |
NextAuth.js v5 Setup
Installation
npm install next-auth@beta
Configuration
// auth.ts
import NextAuth from 'next-auth'
import GitHub from 'next-auth/providers/github'
import Credentials from 'next-auth/providers/credentials'
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
GitHub({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
Credentials({
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' },
},
authorize: async (credentials) => {
const user = await getUserByEmail(credentials.email)
if (!user || !verifyPassword(credentials.password, user.password)) {
return null
}
return user
},
}),
],
callbacks: {
authorized: async ({ auth }) => {
return !!auth
},
},
})
API Route Handler
// app/api/auth/[...nextauth]/route.ts
import { handlers } from '@/auth'
export const { GET, POST } = handlers
Middleware Protection
// middleware.ts
export { auth as middleware } from '@/auth'
export const config = {
matcher: ['/dashboard/:path*', '/api/protected/:path*'],
}
Getting Session Data
In Server Components
// app/dashboard/page.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>
)
}
In Client Components
// components/user-menu.tsx
'use client'
import { useSession } from 'next-auth/react'
export function UserMenu() {
const { data: session, status } = useSession()
if (status === 'loading') {
return <div>Loading...</div>
}
if (!session) {
return <SignInButton />
}
return (
<div>
<span>{session.user?.name}</span>
<SignOutButton />
</div>
)
}
Session Provider Setup
// app/providers.tsx
'use client'
import { SessionProvider } from 'next-auth/react'
export function Providers({ children }: { children: React.ReactNode }) {
return <SessionProvider>{children}</SessionProvider>
}
// app/layout.tsx
import { Providers } from './providers'
export default function RootLayout({ children }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}
Sign In/Out Components
// components/auth-buttons.tsx
import { signIn, signOut } from '@/auth'
export function SignInButton() {
return (
<form
action={async () => {
'use server'
await signIn('github')
}}
>
<button type="submit">Sign in with GitHub</button>
</form>
)
}
export function SignOutButton() {
return (
<form
action={async () => {
'use server'
await signOut()
}}
>
<button type="submit">Sign out</button>
</form>
)
}
Middleware-Based Auth
Basic Pattern
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
const protectedRoutes = ['/dashboard', '/settings', '/api/protected']
const authRoutes = ['/login', '/signup']
export function middleware(request: NextRequest) {
const token = request.cookies.get('session')?.value
const { pathname } = request.nextUrl
// Redirect authenticated users away from auth pages
if (authRoutes.some(route => pathname.startsWith(route))) {
if (token) {
return NextResponse.redirect(new URL('/dashboard', request.url))
}
return NextResponse.next()
}
// Protect routes
if (protectedRoutes.some(route => pathname.startsWith(route))) {
if (!token) {
const loginUrl = new URL('/login', request.url)
loginUrl.searchParams.set('callbackUrl', pathname)
return NextResponse.redirect(loginUrl)
}
}
return NextResponse.next()
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
}
With JWT Verification
// middleware.ts
import { NextResponse } from 'next/server'
import { jwtVerify } from 'jose'
const secret = new TextEncoder().encode(process.env.JWT_SECRET)
export async function middleware(request: NextRequest) {
const token = request.cookies.get('token')?.value
if (!token) {
return NextResponse.redirect(new URL('/login', request.url))
}
try {
const { payload } = await jwtVerify(token, secret)
// Token is valid, continue
return NextResponse.next()
} catch {
// Token is invalid
return NextResponse.redirect(new URL('/login', request.url))
}
}
Role-Based Access Control
Extending Session Types
// types/next-auth.d.ts
import { DefaultSession } from 'next-auth'
declare module 'next-auth' {
interface Session {
user: {
role: 'user' | 'admin'
} & DefaultSession['user']
}
}
// auth.ts
export const { handlers, auth } = NextAuth({
callbacks: {
session: ({ session, token }) => ({
...session,
user: {
...session.user,
role: token.role,
},
}),
jwt: ({ token, user }) => {
if (user) {
token.role = user.role
}
return token
},
},
})
Role-Based Component
// components/admin-only.tsx
import { auth } from '@/auth'
import { redirect } from 'next/navigation'
export async function AdminOnly({ children }: { children: React.ReactNode }) {
const session = await auth()
if (session?.user?.role !== 'admin') {
redirect('/unauthorized')
}
return <>{children}</>
}
// Usage
export default async function AdminPage() {
return (
<AdminOnly>
<AdminDashboard />
</AdminOnly>
)
}
Session Storage Options
JWT (Stateless)
// auth.ts
export const { auth } = NextAuth({
session: { strategy: 'jwt' },
// JWT stored in cookies, no database needed
})
Database Sessions
// auth.ts
import { PrismaAdapter } from '@auth/prisma-adapter'
import { prisma } from '@/lib/prisma'
export const { auth } = NextAuth({
adapter: PrismaAdapter(prisma),
session: { strategy: 'database' },
// Sessions stored in database
})
Custom Login Page
// app/login/page.tsx
'use client'
import { signIn } from 'next-auth/react'
import { useSearchParams } from 'next/navigation'
export default function LoginPage() {
const searchParams = useSearchParams()
const callbackUrl = searchParams.get('callbackUrl') || '/dashboard'
return (
<div className="flex flex-col gap-4">
<button
onClick={() => signIn('github', { callbackUrl })}
className="btn"
>
Sign in with GitHub
</button>
<button
onClick={() => signIn('google', { callbackUrl })}
className="btn"
>
Sign in with Google
</button>
</div>
)
}
Security Best Practices
- Use HTTPS in production
- Set secure cookie flags (HttpOnly, Secure, SameSite)
- Implement CSRF protection (built into NextAuth)
- Validate redirect URLs to prevent open redirects
- Use environment variables for secrets
- Implement rate limiting on auth endpoints
- Hash passwords with bcrypt or argon2
Resources
For detailed patterns, see:
references/middleware-auth.md- Advanced middleware patternsreferences/session-management.md- Session strategiesexamples/nextauth-setup.md- Complete NextAuth.js setup
GitHub Repository
Related Skills
content-collections
MetaThis skill provides a production-tested setup for Content Collections, a TypeScript-first tool that transforms Markdown/MDX files into type-safe data collections with Zod validation. Use it when building blogs, documentation sites, or content-heavy Vite + React applications to ensure type safety and automatic content validation. It covers everything from Vite plugin configuration and MDX compilation to deployment optimization and schema validation.
polymarket
MetaThis skill enables developers to build applications with the Polymarket prediction markets platform, including API integration for trading and market data. It also provides real-time data streaming via WebSocket to monitor live trades and market activity. Use it for implementing trading strategies or creating tools that process live market updates.
creating-opencode-plugins
MetaThis skill helps developers create OpenCode plugins that hook into 25+ event types like commands, files, and LSP operations. It provides the plugin structure, event API specifications, and implementation patterns for JavaScript/TypeScript modules. Use it when you need to intercept, monitor, or extend the OpenCode AI assistant's lifecycle with custom event-driven logic.
cloudflare-turnstile
MetaThis skill provides comprehensive guidance for implementing Cloudflare Turnstile as a CAPTCHA-alternative bot protection system. It covers integration for forms, login pages, API endpoints, and frameworks like React/Next.js/Hono, while handling invisible challenges that maintain user experience. Use it when migrating from reCAPTCHA, debugging error codes, or implementing token validation and E2E tests.
