Аутентификация (Frontend)

В этом разделе описаны механизмы аутентификации пользователей на стороне frontend, включая различные способы входа, регистрации и управления сессиями пользователей.

Обзор аутентификации

Boilerplate использует NextAuth.js для управления аутентификацией на стороне frontend. NextAuth.js предоставляет удобный API для работы с различными провайдерами аутентификации, такими как OAuth (Google, GitHub и т.д.), учетные данные (email/пароль) и Magic Links.

Настройка NextAuth.js

Настройка NextAuth.js находится в файле app/api/auth/[...nextauth]/route.ts. Этот файл определяет конфигурацию аутентификации, включая провайдеров, коллбэки и другие параметры.

typescript
import NextAuth from "next-auth";
import { authOptions } from "@/lib/auth";

const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

Основные настройки аутентификации находятся в файле lib/auth.ts:

typescript
import { PrismaAdapter } from "@auth/prisma-adapter";
import { NextAuthOptions } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import GoogleProvider from "next-auth/providers/google";
import { db } from "@/lib/db";
import { verify } from "@/lib/password";

export const authOptions: NextAuthOptions = {
  adapter: PrismaAdapter(db),
  session: {
    strategy: "jwt",
  },
  pages: {
    signIn: "/login",
  },
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
    CredentialsProvider({
      name: "Учетные данные",
      credentials: {
        email: { label: "Email", type: "email" },
        password: { label: "Пароль", type: "password" },
      },
      async authorize(credentials) {
        if (!credentials?.email || !credentials?.password) {
          return null;
        }

        const user = await db.user.findUnique({
          where: { email: credentials.email },
        });

        if (!user) {
          return null;
        }

        const isPasswordValid = await verify(
          user.password,
          credentials.password
        );

        if (!isPasswordValid) {
          return null;
        }

        return {
          id: user.id,
          email: user.email,
          name: user.name,
        };
      },
    }),
  ],
  callbacks: {
    // Дополнительные коллбэки...
  },
};

Компоненты аутентификации

Форма входа

Компонент формы входа в систему:

tsx
"use client";

import { useState } from "react";
import { signIn } from "next-auth/react";
import { useRouter } from "next/navigation";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";

export function LoginForm() {
  const router = useRouter();
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState("");
  const [isLoading, setIsLoading] = useState(false);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setIsLoading(true);
    setError("");

    try {
      const result = await signIn("credentials", {
        email,
        password,
        redirect: false,
      });

      if (result?.error) {
        setError("Неверный email или пароль");
        setIsLoading(false);
        return;
      }

      router.push("/dashboard");
    } catch (error) {
      setError("Произошла ошибка при входе. Попробуйте снова.");
      setIsLoading(false);
    }
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      <div className="space-y-2">
        <Label htmlFor="email">Email</Label>
        <Input
          id="email"
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          placeholder="you@example.com"
          required
        />
      </div>
      <div className="space-y-2">
        <Label htmlFor="password">Пароль</Label>
        <Input
          id="password"
          type="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          required
        />
      </div>
      {error && <p className="text-red-500 text-sm">{error}</p>}
      <Button type="submit" className="w-full" disabled={isLoading}>
        {isLoading ? "Вход..." : "Войти"}
      </Button>
    </form>
  );
}

Кнопка для входа через Google

tsx
"use client";

import { signIn } from "next-auth/react";
import { Button } from "@/components/ui/button";
import { FcGoogle } from "react-icons/fc";

export function GoogleLoginButton() {
  return (
    <Button
      variant="outline"
      className="w-full"
      onClick={() => signIn("google", { callbackUrl: "/dashboard" })}
    >
      <FcGoogle className="mr-2 h-5 w-5" />
      Войти через Google
    </Button>
  );
}

Защита маршрутов

Для защиты приватных маршрутов от неавторизованных пользователей можно использовать middleware:

typescript
// middleware.ts
import { withAuth } from "next-auth/middleware";
import { NextResponse } from "next/server";

export default withAuth(
  function middleware(req) {
    // Дополнительная логика авторизации
    return NextResponse.next();
  },
  {
    callbacks: {
      authorized({ token }) {
        return !!token;
      },
    },
  }
);

export const config = {
  matcher: ["/dashboard/:path*", "/settings/:path*", "/protected/:path*"],
};

Получение данных текущего пользователя

Для доступа к данным текущего пользователя в клиентских компонентах можно использовать хук useSession:

tsx
"use client";

import { useSession } from "next-auth/react";

export function ProfileInfo() {
  const { data: session, status } = useSession();

  if (status === "loading") {
    return <div>Загрузка...</div>;
  }

  if (status === "unauthenticated") {
    return <div>Пожалуйста, войдите в свой аккаунт</div>;
  }

  return (
    <div>
      <h2>Профиль</h2>
      <p>Имя: {session?.user?.name}</p>
      <p>Email: {session?.user?.email}</p>
    </div>
  );
}

В серверных компонентах можно использовать функцию getServerSession:

tsx
import { getServerSession } from "next-auth/next";
import { authOptions } from "@/lib/auth";

export default async function DashboardPage() {
  const session = await getServerSession(authOptions);

  if (!session) {
    // Перенаправление или отображение сообщения
    return <div>Пожалуйста, войдите в свой аккаунт.</div>;
  }

  return (
    <div>
      <h1>Добро пожаловать, {session.user.name}!</h1>
      {/* Содержимое панели управления */}
    </div>
  );
}

Выход из системы

Компонент для выхода из системы:

tsx
"use client";

import { signOut } from "next-auth/react";
import { Button } from "@/components/ui/button";

export function LogoutButton() {
  return (
    <Button
      variant="ghost"
      onClick={() => signOut({ callbackUrl: "/" })}
    >
      Выйти
    </Button>
  );
}

Дополнительные возможности

  • Настройка страниц ошибок аутентификации
  • Интеграция Magic Links для входа без пароля
  • Добавление двухфакторной аутентификации (2FA)
  • Настройка сессий и токенов JWT
  • Интеграция с другими OAuth провайдерами (GitHub, Facebook, Twitter)

Полезные ресурсы