0
Nextauth setup
npm next-auth@beta'yı yükleyin
yazan kuscubasiece·8 Eylül 2026
Kaynak: davepoon/claude-code-subagents-collection · MIT
npm next-auth@beta'yı yükleyin
Kaynak: davepoon/claude-code-subagents-collection · MIT
mkdir -p .claude/skills/nextauth-setup && curl -sL https://www.antrophiclove.com/api/kaynak/nextauth-setup/raw -o .claude/skills/nextauth-setup/SKILL.mdHedef dosya: .claude/skills/nextauth-setup/SKILL.md
# NextAuth.js v5 (Auth.js) Setup
## Installation
```bash
npm install next-auth@beta
```
## Basic Configuration
### Auth Configuration
```tsx
// auth.ts
import NextAuth from 'next-auth'
import GitHub from 'next-auth/providers/github'
import Google from 'next-auth/providers/google'
import Credentials from 'next-auth/providers/credentials'
import { PrismaAdapter } from '@auth/prisma-adapter'
import { prisma } from '@/lib/prisma'
import bcrypt from 'bcryptjs'
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [
GitHub({
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
}),
Google({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
Credentials({
name: 'credentials',
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials) {
if (!credentials?.email || !credentials?.password) {
throw new Error('Email and password required')
}
const user = await prisma.user.findUnique({
where: { email: credentials.email as string },
})
if (!user || !user.password) {
throw new Error('Invalid credentials')
}
const isValid = await bcrypt.compare(
credentials.password as string,
user.password
)
if (!isValid) {
throw new Error('Invalid credentials')
}
return {
id: user.id,
email: user.email,
name: user.name,
image: user.image,
}
},
}),
],
session: {
strategy: 'jwt',
},
pages: {
signIn: '/login',
error: '/login',
},
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id
}
return token
},
async session({ session, token }) {
if (session.user) {
session.user.id = token.id as string
}
return session
},
},
})
```
### Route Handler
```tsx
// app/api/auth/[...nextauth]/route.ts
import { handlers } from '@/auth'
export const { GET, POST } = handlers
```
### Middleware
```tsx
// middleware.ts
import { auth } from '@/auth'
export default auth((req) => {
const isLoggedIn = !!req.auth
const { nextUrl } = req
const isAuthRoute = nextUrl.pathname.startsWith('/login') ||
nextUrl.pathname.startsWith('/register')
const isProtectedRoute = nextUrl.pathname.startsWith('/dashboard') ||
nextUrl.pathname.startsWith('/settings')
// Redirect authenticated users from auth pages
if (isAuthRoute && isLoggedIn) {
return Response.redirect(new URL('/dashboard', nextUrl))
}
// Redirect unauthenticated users from protected pages
if (isProtectedRoute && !isLoggedIn) {
const callbackUrl = encodeURIComponent(nextUrl.pathname)
return Response.redirect(new URL(`/login?callbackUrl=${callbackUrl}`, nextUrl))
}
})
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
}
```
## Extended Types
```tsx
// types/next-auth.d.ts
import { DefaultSession, DefaultUser } from 'next-auth'
import { JWT, DefaultJWT } from 'next-auth/jwt'
declare module 'next-auth' {
interface Session {
user: {
id: string
role: string
} & DefaultSession['user']
}
interface User extends DefaultUser {
role: string
}
}
declare module 'next-auth/jwt' {
interface JWT extends DefaultJWT {
id: string
role: string
}
}
```
### Updated Callbacks
```tsx
// auth.ts
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
session.user.role = token.role
}
return session
},
},
```
## Server-Side Usage
### In Server Components
```tsx
// app/dashboard/page.tsx
import { auth } from '@/auth'
import { redirect } from 'next/navigation'
export default async function DashboardPage() {
const session = await auth()
if (!session?.user) {
redirect('/login')
}
return (
<div>
<h1>Welcome, {session.user.name}</h1>
<p>Email: {session.user.email}</p>
<p>Role: {session.user.role}</p>
</div>
)
}
```
### In Server Actions
```tsx
// actions/posts.ts
'use server'
import { auth } from '@/auth'
import { revalidatePath } from 'next/cache'
export async function createPost(formData: FormData) {
const session = await auth()
if (!session?.user) {
throw new Error('Unauthorized')
}
await prisma.post.create({
data: {
title: formData.get('title') as string,
content: formData.get('content') as string,
authorId: session.user.id,
},
})
revalidatePath('/posts')
}
```
## Client-Side Usage
### 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,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}
```
### Using Session Hook
```tsx
// components/user-menu.tsx
'use client'
import { useSession, signOut } from 'next-auth/react'
import Link from 'next/link'
export function UserMenu() {
const { data: session, status } = useSession()
if (status === 'loading') {
return <div>Loading...</div>
}
if (!session) {
return (
<div className="flex gap-4">
<Link href="/login">Sign In</Link>
<Link href="/register">Sign Up</Link>
</div>
)
}
return (
<div className="flex items-center gap-4">
{session.user.image && (
<img
src={session.user.image}
alt={session.user.name || ''}
className="w-8 h-8 rounded-full"
/>
)}
<span>{session.user.name}</span>
<button onClick={() => signOut({ callbackUrl: '/' })}>
Sign Out
</button>
</div>
)
}
```
## Sign In Forms
### OAuth Buttons
```tsx
// components/oauth-buttons.tsx
'use client'
import { signIn } from 'next-auth/react'
export function OAuthButtons() {
return (
<div className="space-y-2">
<button
onClick={() => signIn('github', { callbackUrl: '/dashboard' })}
className="w-full flex items-center justify-center gap-2 px-4 py-2 border rounded"
>
<GitHubIcon />
Continue with GitHub
</button>
<button
onClick={() => signIn('google', { callbackUrl: '/dashboard' })}
className="w-full flex items-center justify-center gap-2 px-4 py-2 border rounded"
>
<GoogleIcon />
Continue with Google
</button>
</div>
)
}
```
### Credentials Form
```tsx
// components/login-form.tsx
'use client'
import { signIn } from 'next-auth/react'
import { useSearchParams, useRouter } from 'next/navigation'
import { useState } from 'react'
export function LoginForm() {
const router = useRouter()
const searchParams = useSearchParams()
const callbackUrl = searchParams.get('callbackUrl') || '/dashboard'
const [error, setError] = useState<string | null>(null)
const [loading, setLoading] = useState(false)
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault()
setLoading(true)
setError(null)
const formData = new FormData(e.currentTarget)
const result = await signIn('credentials', {
email: formData.get('email'),
password: formData.get('password'),
redirect: false,
})
setLoading(false)
if (result?.error) {
setError('Invalid email or password')
} else {
router.push(callbackUrl)
router.refresh()
}
}
return (
<form onSubmit={handleSubmit} className="space-y-4">
{error && (
<div className="bg-red-100 text-red-700 p-3 rounded">{error}</div>
)}
<div>
<label htmlFor="email">Email</label>
<input
id="email"
name="email"
type="email"
required
className="w-full px-3 py-2 border rounded"
/>
</div>
<div>
<label htmlFor="password">Password</label>
<input
id="password"
name="password"
type="password"
required
className="w-full px-3 py-2 border rounded"
/>
</div>
<button
type="submit"
disabled={loading}
className="w-full py-2 bg-blue-600 text-white rounded disabled:opacity-50"
>
{loading ? 'Signing in...' : 'Sign In'}
</button>
</form>
)
}
```
## Registration
```tsx
// actions/register.ts
'use server'
import { prisma } from '@/lib/prisma'
import bcrypt from 'bcryptjs'
import { z } from 'zod'
const registerSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
password: z.string().min(8),
})
export async function register(formData: FormData) {
const validated = registerSchema.safeParse({
name: formData.get('name'),
email: formData.get('email'),
password: formData.get('password'),
})
if (!validated.success) {
return { error: 'Invalid input' }
}
const { name, email, password } = validated.data
// Check if user exists
const existing = await prisma.user.findUnique({
where: { email },
})
if (existing) {
return { error: 'Email already registered' }
}
// Hash password
const hashedPassword = await bcrypt.hash(password, 12)
// Create user
await prisma.user.create({
data: {
name,
email,
password: hashedPassword,
},
})
return { success: true }
}
```
## Role-Based Access
### Protected Component
```tsx
// 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) {
redirect('/login')
}
if (session.user.role !== 'admin') {
redirect('/unauthorized')
}
return <>{children}</>
}
// Usage
// app/admin/page.tsx
import { AdminOnly } from '@/components/admin-only'
export default function AdminPage() {
return (
<AdminOnly>
<h1>Admin Dashboard</h1>
{/* Admin content */}
</AdminOnly>
)
}
```
### Role Check Helper
```tsx
// lib/auth-helpers.ts
import { auth } from '@/auth'
export async function requireAuth() {
const session = await auth()
if (!session?.user) {
throw new Error('Unauthorized')
}
return session
}
export async function requireRole(role: string) {
const session = await requireAuth()
if (session.user.role !== role) {
throw new Error('Forbidden')
}
return session
}
// Usage in Server Actions
export async function adminAction() {
const session = await requireRole('admin')
// ... admin-only logic
}
```
## Prisma Schema
```prisma
// prisma/schema.prisma
model User {
id String @id @default(cuid())
name String?
email String @unique
emailVerified DateTime?
image String?
password String?
role String @default("user")
accounts Account[]
sessions Session[]
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Account {
id String @id @default(cuid())
userId String
type String
provider String
providerAccountId String
refresh_token String? @db.Text
access_token String? @db.Text
expires_at Int?
token_type String?
scope String?
id_token String? @db.Text
session_state String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
}
model Session {
id String @id @default(cuid())
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model VerificationToken {
identifier String
token String @unique
expires DateTime
@@unique([identifier, token])
}
```
## Environment Variables
```env
# .env.local
AUTH_SECRET=your-secret-key-here
AUTH_URL=http://localhost:3000
# GitHub OAuth
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
# Google OAuth
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
# Database
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
```