Что такое NextAuth.js
NextAuth.js (теперь Auth.js) — библиотека аутентификации для Next.js. Она поддерживает OAuth провайдеры (Google, GitHub), JWT токены, сессии и credentials аутентификацию.
Установка и настройка
npm install next-auth @auth/prisma-adapter
// 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 { PrismaAdapter } from '@auth/prisma-adapter';
import GoogleProvider from 'next-auth/providers/google';
import GitHubProvider from 'next-auth/providers/github';
import CredentialsProvider from 'next-auth/providers/credentials';
import { prisma } from './prisma';
import bcrypt from 'bcrypt';
export const authOptions: NextAuthOptions = {
adapter: PrismaAdapter(prisma),
providers: [
// OAuth провайдеры
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
GitHubProvider({
clientId: process.env.GITHUB_ID!,
clientSecret: process.env.GITHUB_SECRET!,
}),
// Email/Password
CredentialsProvider({
name: 'credentials',
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' }
},
async authorize(credentials) {
if (!credentials?.email || !credentials?.password) {
throw new Error('Invalid credentials');
}
const user = await prisma.user.findUnique({
where: { email: credentials.email }
});
if (!user || !user.password) {
throw new Error('Invalid credentials');
}
const isValid = await bcrypt.compare(
credentials.password,
user.password
);
if (!isValid) {
throw new Error('Invalid credentials');
}
return {
id: user.id,
email: user.email,
name: user.name,
role: user.role
};
}
})
],
session: {
strategy: 'jwt', // или 'database'
maxAge: 30 * 24 * 60 * 60, // 30 дней
},
callbacks: {
async jwt({ token, user }) {
if (user) {
token.role = user.role;
token.id = user.id;
}
return token;
},
async session({ session, token }) {
if (session.user) {
session.user.role = token.role;
session.user.id = token.id;
}
return session;
}
},
pages: {
signIn: '/login',
error: '/login',
}
};
Компоненты аутентификации
// components/LoginForm.tsx
'use client';
import { signIn } from 'next-auth/react';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
export function LoginForm() {
const router = useRouter();
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setLoading(true);
setError('');
const formData = new FormData(e.currentTarget);
const result = await signIn('credentials', {
email: formData.get('email'),
password: formData.get('password'),
redirect: false
});
if (result?.error) {
setError('Неверный email или пароль');
setLoading(false);
} else {
router.push('/dashboard');
router.refresh();
}
};
return (
<form onSubmit={handleSubmit}>
{error && <div className="error">{error}</div>}
<input name="email" type="email" placeholder="Email" required />
<input name="password" type="password" placeholder="Пароль" required />
<button type="submit" disabled={loading}>
{loading ? 'Вход...' : 'Войти'}
</button>
<div className="divider">или</div>
<button type="button" onClick={() => signIn('google')}>
Войти через Google
</button>
<button type="button" onClick={() => signIn('github')}>
Войти через GitHub
</button>
</form>
);
}
// components/UserMenu.tsx
'use client';
import { useSession, signOut } from 'next-auth/react';
export function UserMenu() {
const { data: session, status } = useSession();
if (status === 'loading') {
return <Skeleton />;
}
if (!session) {
return <Link href="/login">Войти</Link>;
}
return (
<div className="user-menu">
<img src={session.user.image} alt={session.user.name} />
<span>{session.user.name}</span>
<button onClick={() => signOut()}>Выйти</button>
</div>
);
}
Защита роутов
// 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;
// Проверка роли для админки
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*', '/profile/:path*']
};
// Защита Server Component
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('/login');
}
return <Dashboard user={session.user} />;
}
// Защита API Route
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
export async function GET(request: Request) {
const session = await getServerSession(authOptions);
if (!session) {
return new Response('Unauthorized', { status: 401 });
}
// Логика API
return Response.json({ data: '...' });
}
Типизация сессии
// types/next-auth.d.ts
import { DefaultSession } from 'next-auth';
declare module 'next-auth' {
interface Session {
user: {
id: string;
role: 'USER' | 'ADMIN';
} & DefaultSession['user'];
}
interface User {
role: 'USER' | 'ADMIN';
}
}
declare module 'next-auth/jwt' {
interface JWT {
id: string;
role: 'USER' | 'ADMIN';
}
}
Заключение
NextAuth.js упрощает аутентификацию в Next.js. OAuth провайдеры настраиваются за минуты, credentials требуют больше работы. Используйте JWT для stateless сессий, database adapter для полного контроля.
Безопасность — приоритет. Всегда хэшируйте пароли, используйте HTTPS, настройте CSRF защиту.
