NextAuth.js / Auth.js authentication patterns
Implements authentication patterns for Next.js applications using NextAuth.js and Auth.js.
/plugin marketplace add the-answerai/alphaagent-team/plugin install aai-stack-nextjs@alphaagent-teamThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Patterns for implementing authentication with NextAuth.js / Auth.js.
// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth'
import { authOptions } from '@/lib/auth'
const handler = NextAuth(authOptions)
export { handler as GET, handler as POST }
// lib/auth.ts
import { NextAuthOptions } from 'next-auth'
import CredentialsProvider from 'next-auth/providers/credentials'
import GoogleProvider from 'next-auth/providers/google'
export const authOptions: NextAuthOptions = {
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
CredentialsProvider({
name: 'Credentials',
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials) {
const user = await verifyCredentials(
credentials?.email,
credentials?.password
)
if (user) return user
return null
},
}),
],
pages: {
signIn: '/auth/signin',
signOut: '/auth/signout',
error: '/auth/error',
},
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id
token.role = user.role
}
return token
},
async session({ session, token }) {
session.user.id = token.id as string
session.user.role = token.role as string
return session
},
},
session: {
strategy: 'jwt',
},
}
// types/next-auth.d.ts
import { DefaultSession } from 'next-auth'
declare module 'next-auth' {
interface Session {
user: {
id: string
role: string
} & DefaultSession['user']
}
interface User {
role: string
}
}
declare module 'next-auth/jwt' {
interface JWT {
id: string
role: string
}
}
// app/dashboard/page.tsx
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
import { redirect } from 'next/navigation'
export default async function DashboardPage() {
const session = await getServerSession(authOptions)
if (!session) {
redirect('/auth/signin')
}
return (
<div>
<h1>Welcome, {session.user.name}</h1>
<p>Role: {session.user.role}</p>
</div>
)
}
// app/api/protected/route.ts
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
import { NextResponse } from 'next/server'
export async function GET() {
const session = await getServerSession(authOptions)
if (!session) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
)
}
// User is authenticated
return NextResponse.json({
user: session.user,
data: await getProtectedData(session.user.id),
})
}
// 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>
)
}
'use client'
import { useSession, signIn, signOut } from 'next-auth/react'
export function AuthButton() {
const { data: session, status } = useSession()
if (status === 'loading') {
return <div>Loading...</div>
}
if (session) {
return (
<div>
<span>Welcome, {session.user.name}</span>
<button onClick={() => signOut()}>Sign Out</button>
</div>
)
}
return <button onClick={() => signIn()}>Sign In</button>
}
'use client'
import { useSession } from 'next-auth/react'
import { useRouter } from 'next/navigation'
import { useEffect } from 'react'
export function ProtectedComponent({ children }: { children: React.ReactNode }) {
const { status } = useSession()
const router = useRouter()
useEffect(() => {
if (status === 'unauthenticated') {
router.push('/auth/signin')
}
}, [status, router])
if (status === 'loading') {
return <div>Loading...</div>
}
if (status === 'authenticated') {
return <>{children}</>
}
return null
}
// middleware.ts
import { withAuth } from 'next-auth/middleware'
import { NextResponse } from 'next/server'
export default withAuth(
function middleware(req) {
const token = req.nextauth.token
const path = req.nextUrl.pathname
// Role-based access control
if (path.startsWith('/admin') && token?.role !== 'admin') {
return NextResponse.redirect(new URL('/unauthorized', req.url))
}
return NextResponse.next()
},
{
callbacks: {
authorized: ({ token }) => !!token,
},
}
)
export const config = {
matcher: ['/dashboard/:path*', '/admin/:path*', '/account/:path*'],
}
// app/auth/signin/page.tsx
'use client'
import { signIn } from 'next-auth/react'
import { useSearchParams } from 'next/navigation'
import { useState } from 'react'
export default function SignIn() {
const searchParams = useSearchParams()
const callbackUrl = searchParams.get('callbackUrl') || '/dashboard'
const error = searchParams.get('error')
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
await signIn('credentials', {
email,
password,
callbackUrl,
})
}
return (
<div className="signin-page">
<h1>Sign In</h1>
{error && <div className="error">Authentication failed</div>}
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
<button type="submit">Sign In</button>
</form>
<div className="divider">or</div>
<button onClick={() => signIn('google', { callbackUrl })}>
Sign in with Google
</button>
</div>
)
}
// lib/auth.ts
import { PrismaAdapter } from '@auth/prisma-adapter'
import { prisma } from '@/lib/prisma'
export const authOptions: NextAuthOptions = {
adapter: PrismaAdapter(prisma),
session: {
strategy: 'database', // Store sessions in database
},
providers: [...],
callbacks: {
async session({ session, user }) {
session.user.id = user.id
session.user.role = user.role
return session
},
},
}
callbacks: {
async jwt({ token, user, account, trigger, session }) {
// Initial sign in
if (user) {
token.id = user.id
token.role = user.role
}
// Update triggered from client
if (trigger === 'update' && session) {
token.name = session.name
}
// Refresh token
if (account?.provider === 'google') {
token.accessToken = account.access_token
}
return token
},
}
callbacks: {
async session({ session, token, user }) {
// JWT strategy
if (token) {
session.user.id = token.id
session.user.role = token.role
}
// Database strategy
if (user) {
session.user.id = user.id
session.user.role = user.role
}
return session
},
}
callbacks: {
async signIn({ user, account, profile }) {
// Block certain emails
if (user.email?.endsWith('@blocked.com')) {
return false
}
// Allow OAuth but verify email for credentials
if (account?.provider === 'credentials') {
const dbUser = await prisma.user.findUnique({
where: { email: user.email },
})
if (!dbUser?.emailVerified) {
return '/auth/verify-email' // Redirect
}
}
return true
},
}
events: {
async signIn({ user, account, isNewUser }) {
if (isNewUser) {
await sendWelcomeEmail(user.email)
}
await logSignIn(user.id, account?.provider)
},
async signOut({ token }) {
await logSignOut(token.id)
},
async createUser({ user }) {
await initializeUserDefaults(user.id)
},
}
Used by:
frontend-developer agentbackend-developer agentfullstack-developer agentExpert guidance for Next.js Cache Components and Partial Prerendering (PPR). **PROACTIVE ACTIVATION**: Use this skill automatically when working in Next.js projects that have `cacheComponents: true` in their next.config.ts/next.config.js. When this config is detected, proactively apply Cache Components patterns and best practices to all React Server Component implementations. **DETECTION**: At the start of a session in a Next.js project, check for `cacheComponents: true` in next.config. If enabled, this skill's patterns should guide all component authoring, data fetching, and caching decisions. **USE CASES**: Implementing 'use cache' directive, configuring cache lifetimes with cacheLife(), tagging cached data with cacheTag(), invalidating caches with updateTag()/revalidateTag(), optimizing static vs dynamic content boundaries, debugging cache issues, and reviewing Cache Component implementations.