---
name: "NextAuth.js Authentication"
description: "Implement authentication with NextAuth.js v5, Google OAuth, credentials provider, session management, and protected routes. Apply when building auth flows, protecting routes, managing sessions, or implementing RBAC."
allowed-tools: Read, Write, Edit, Bash
version: 1.1.0
compatibility: Claude Opus 4.5, Claude Code v2.x
updated: 2026-01-24
---
# NextAuth.js Authentication
Systematic authentication implementation with NextAuth.js v5, OAuth providers, and session management.
## Overview
This Skill enforces:
- NextAuth.js v5 configuration
- Google OAuth and credentials providers
- Session management with JWT
- Protected routes with middleware
- Role-based access control (RBAC)
- Secure password hashing
- Token refresh strategies
Apply when implementing authentication, protecting routes, or managing user sessions.
## Setup NextAuth.js v5
### Install Dependencies
```bash
npm install next-auth@beta bcryptjs
npm install -D @types/bcryptjs
```
### Create Auth Configuration
```ts
// lib/auth.ts
import NextAuth from 'next-auth';
import Google from 'next-auth/providers/google';
import Credentials from 'next-auth/providers/credentials';
import { PrismaAdapter } from '@auth/prisma-adapter';
import { db } from '@/lib/db';
import bcrypt from 'bcryptjs';
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: PrismaAdapter(db),
session: { strategy: 'jwt' },
providers: [
Google({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!
}),
Credentials({
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' }
},
async authorize(credentials) {
if (!credentials?.email || !credentials?.password) {
return null;
}
const user = await db.user.findUnique({
where: { email: credentials.email as string }
});
if (!user || !user.passwordHash) {
return null;
}
const isValid = await bcrypt.compare(
credentials.password as string,
user.passwordHash
);
if (!isValid) {
return null;
}
return {
id: user.id,
email: user.email,
name: user.name,
role: user.role
};
}
})
],
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id;
token.role = user.role;
}
return token;
},
async session({ session, token }) {
if (session.user) {
session.user.id = token.id as string;
session.user.role = token.role as string;
}
return session;
}
},
pages: {
signIn: '/login',
error: '/auth/error'
},
secret: process.env.NEXTAUTH_SECRET
});
```
### Environment Variables
```
# .env.local
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=your-secret-key-here
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
```
## Google OAuth Setup
### Create Google OAuth Credentials
1. Go to [Google Cloud Console](https://console.cloud.google.com/)
2. Create new project or select existing
3. Enable Google+ API
4. Go to Credentials → Create Credentials → OAuth 2.0 Client ID
5. Set authorized redirect URIs:
- Development: `http://localhost:3000/api/auth/callback/google`
- Production: `https://yourdomain.com/api/auth/callback/google`
### Configure Google Provider
```ts
// lib/auth.ts
Google({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
authorization: {
params: {
prompt: 'consent',
access_type: 'offline',
response_type: 'code'
}
}
})
```
## API Route Handler
```ts
// app/api/auth/[...nextauth]/route.ts
import { handlers } from '@/lib/auth';
export const { GET, POST } = handlers;
```
## Sign In Page
```tsx
// app/login/page.tsx
'use client';
import { signIn } from 'next-auth/react';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
export default function LoginPage() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const router = useRouter();
const handleCredentialsSignIn = async (e: React.FormEvent) => {
e.preventDefault();
const result = await signIn('credentials', {
email,
password,
redirect: false
});
if (result?.error) {
setError('Invalid credentials');
} else {
router.push('/dashboard');
}
};
const handleGoogleSignIn = () => {
signIn('google', { callbackUrl: '/dashboard' });
};
return (
Sign In
{/* Google Sign In */}
OR
{/* Credentials Sign In */}
);
}
```
## Sign Up (Registration)
```tsx
// app/api/auth/register/route.ts
import { NextResponse } from 'next/server';
import bcrypt from 'bcryptjs';
import { db } from '@/lib/db';
export async function POST(request: Request) {
try {
const { email, password, name } = await request.json();
// Validate input
if (!email || !password || !name) {
return NextResponse.json(
{ error: 'Missing required fields' },
{ status: 400 }
);
}
// Check if user exists
const existingUser = await db.user.findUnique({
where: { email }
});
if (existingUser) {
return NextResponse.json(
{ error: 'User already exists' },
{ status: 409 }
);
}
// Hash password
const passwordHash = await bcrypt.hash(password, 10);
// Create user
const user = await db.user.create({
data: {
email,
name,
passwordHash
}
});
return NextResponse.json(
{ message: 'User created', userId: user.id },
{ status: 201 }
);
} catch (error) {
return NextResponse.json(
{ error: 'Failed to create user' },
{ status: 500 }
);
}
}
```
## Protected Routes with Middleware
```ts
// middleware.ts
import { auth } from '@/lib/auth';
import { NextResponse } from 'next/server';
export default auth((req) => {
const { pathname } = req.nextUrl;
const session = req.auth;
// Public routes
const publicRoutes = ['/', '/login', '/register'];
if (publicRoutes.includes(pathname)) {
return NextResponse.next();
}
// Require authentication
if (!session) {
return NextResponse.redirect(new URL('/login', req.url));
}
// Admin-only routes
if (pathname.startsWith('/admin')) {
if (session.user.role !== 'admin') {
return NextResponse.redirect(new URL('/dashboard', req.url));
}
}
return NextResponse.next();
});
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)']
};
```
## Server-Side Session Access
```tsx
// app/dashboard/page.tsx
import { auth } from '@/lib/auth';
import { redirect } from 'next/navigation';
export default async function DashboardPage() {
const session = await auth();
if (!session) {
redirect('/login');
}
return (
Dashboard
Welcome, {session.user.name}!
Email: {session.user.email}
Role: {session.user.role}
);
}
```
## Client-Side Session Access
```tsx
// app/components/UserProfile.tsx
'use client';
import { useSession, signOut } from 'next-auth/react';
export function UserProfile() {
const { data: session, status } = useSession();
if (status === 'loading') {
return Loading...
;
}
if (!session) {
return Not signed in
;
}
return (
{session.user.name}
{session.user.email}
);
}
```
## Role-Based Access Control (RBAC)
```tsx
// lib/rbac.ts
export const PERMISSIONS = {
admin: ['create', 'read', 'update', 'delete'],
editor: ['create', 'read', 'update'],
viewer: ['read']
};
export function hasPermission(role: string, action: string): boolean {
return PERMISSIONS[role as keyof typeof PERMISSIONS]?.includes(action) ?? false;
}
// Usage in Server Component
import { auth } from '@/lib/auth';
import { hasPermission } from '@/lib/rbac';
export default async function Page() {
const session = await auth();
if (!session) {
redirect('/login');
}
const canDelete = hasPermission(session.user.role, 'delete');
return (
{canDelete && }
);
}
```
## Session Provider for Client Components
```tsx
// app/providers.tsx
'use client';
import { SessionProvider } from 'next-auth/react';
export function Providers({ children }: { children: React.ReactNode }) {
return {children};
}
// app/layout.tsx
import { Providers } from './providers';
export default function RootLayout({
children
}: {
children: React.ReactNode;
}) {
return (
{children}
);
}
```
## Anti-Patterns
```tsx
// ❌ BAD: Storing password plaintext
const user = await db.user.create({
data: {
password: password // NEVER!
}
});
// ✅ GOOD: Hash password
const passwordHash = await bcrypt.hash(password, 10);
const user = await db.user.create({
data: { passwordHash }
});
// ❌ BAD: No session check
export default function DashboardPage() {
return Dashboard
;
}
// ✅ GOOD: Check session
export default async function DashboardPage() {
const session = await auth();
if (!session) redirect('/login');
return Dashboard
;
}
// ❌ BAD: Client-side only protection
'use client';
export default function Page() {
const { data: session } = useSession();
if (!session) return Not allowed
;
// Still accessible by disabling JS!
}
// ✅ GOOD: Server-side protection
export default async function Page() {
const session = await auth();
if (!session) redirect('/login');
return Protected content
;
}
```
## Verification Before Production
- [ ] NextAuth.js v5 configured
- [ ] Google OAuth credentials set up
- [ ] Credentials provider working
- [ ] Passwords hashed with bcrypt (never plaintext)
- [ ] Session management with JWT
- [ ] Middleware protecting routes
- [ ] RBAC implemented for admin routes
- [ ] Sign in/sign up pages functional
- [ ] Session provider wraps app
- [ ] Environment variables configured
- [ ] NEXTAUTH_SECRET set (never commit)
## Integration with Project Standards
Enforces security best practices:
- S-1: Passwords hashed with bcrypt
- S-5: Secrets in environment variables
- AP-1: Authentication required for admin routes
- S-8: RBAC for authorization
## Resources
- NextAuth.js v5: https://authjs.dev
- Google OAuth Setup: https://console.cloud.google.com
- Prisma Adapter: https://authjs.dev/reference/adapter/prisma
---
**Last Updated:** January 24, 2026
**Compatibility:** Claude Opus 4.5, Claude Code v2.x
**Status:** Production Ready
> **January 2026 Update:** This skill is compatible with Claude Opus 4.5 and Claude Code v2.x. For complex tasks, use the `effort: high` parameter for thorough analysis.