nextjs-auth
NextAuth.js / Auth.js authentication patterns
What this skill does
# Next.js Auth Skill
Patterns for implementing authentication with NextAuth.js / Auth.js.
## Setup
### Basic Configuration
```typescript
// 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 }
```
```typescript
// 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',
},
}
```
### Type Extensions
```typescript
// 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
}
}
```
## Server-Side Auth
### Getting Session in Server Components
```tsx
// 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>
)
}
```
### Auth in API Routes
```typescript
// 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),
})
}
```
## Client-Side Auth
### Session Provider
```tsx
// 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>
)
}
```
### useSession Hook
```tsx
'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>
}
```
### Protected Client Component
```tsx
'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 Protection
```typescript
// 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*'],
}
```
## Custom Sign In Page
```tsx
// 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>
)
}
```
## Database Sessions
```typescript
// 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
### JWT Callback
```typescript
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
},
}
```
### Session Callback
```typescript
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
},
}
```
### SignRelated in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.