Галоўная / Артыкулы

Авталогін у Google з Auth.js і Prisma: супагоджэнне нарадзіваў Next.js

Спаўніце нарады Prisma і Auth.js для Next.js у адну працуючую аплікацыю з можласцю абмовкі праз Google, запам’ятаваннем пользователяў і пасоў, прычалёванных да сесіі, уключаючы таксама выправленні, якія працявалі не паведамляюць.

Выдавец: REACTAPP.TOP10 хв чытання2404 слоў

Крыніцы: Prisma + NextAuth setup for NextJS

Для дадзення адказаў і захаваных дадзэнняў у прыкладе аплікацыі Next.js не трэба окольнага бэкенду. За дапамогою Auth.js (прыемніка NextAuth) для аутэнтыфікацыі, Prisma як ORM і хоставанага база дадзэння Postgres, такога як Neon, той самы проект Next.js можа кераваць процесам входу, сеансамі і захаваннем дадзэнняў. Кожны з інструментаў мае свой адпаведны падчыягнік для Next.js, але гіды не пагоджваюцца ў структуре папак, апісваюць застарэлыя стандарты і ніколі не паказваюць, як усе гэта з’едынаецца. Што да гіда па кроках, ён следуе за тымі гідамі і паказвае кожную неабходную настройку, тады ў рэзультате у вас будзе можлівасць входу через Google, дадзеныя корыстнікаў захаваныя ў Postgres і пасткі, прызначаныя для аутэнтыфікованага корыстніка.

Чаму трэба захавваць аутэнтыфікацыю і дадзеныя ў самай аплікацыі Next.js

Звычным раштункам для продукту, які спачатку ўжо ёсць пераважна інструментам на боку кліента, ёсць адзінэйчы бэкенд – напрыклад, Django з Postgres і прыёмнік айдэнтыкацыйных дадзеных, такі як Auth0. Це працюе, але значыць неабходна ўстановка і адпаведная аператыва з другім сервісам, які трэба стежыць і за які трэба плаціць. Калі маленькі продукт патрэбуе дадаць такія функцыі, як збераганне рабочых праграм, викорыстоўванне сервера Next.js для айдэнтыкацый і доступу да базы дадзеных дапамага зменшыць вартасці хоставання і колькасць элементаў, якія трэба адсталяваты. Але ў такім случае трэба самостоятельна кераваць інтеграцыяй трох праектов, пра што і пішана рэшта гэтага артыкула.

Гэтыя пакеты быстра змінююцца. Auth.js для Next.js яшчэ публікуецца пад тагам beta, а Prisma і Next.js зменілі свае стандартныя настройкі ў няўжо выданых версіях. Спрыймайце наведзеныя нижэй крокі як адказ і пераканайцеся ў деталях, супрацоўваючы з актуальной дакументацыяй кожнага праекту.

Установка Prisma без дадатковых прыёмнікаў

Інструкцыя Prisma для Next.js напісана з урахоўванням Prisma Postgres – власнай хоставаной базы дадзеных компаніі. Якщо вы плануеце викорыстоўваць іншую базу дадзеных Postgres, вы можете прыбыць @prisma/extension-accelerate, які падходзіць толькі для гэтага хоставанага рашэння.

Для звычной наладкі Postgres вам патрэбны два інсталляцыі: CLI і tsx як залежнасці для разработки, а таксама кліент і dotenv як залежнасці пад час выканання. У фрагменте паказаныя оба каманды на адной лініі; запускайце іх адна за іншай:

npm install prisma tsx --save-dev npm install @prisma/client dotenv

Далей ініцыялізуйце Prisma ў проекте:

npx prisma init

У інструкцыі таксама згадваецца флаг --db для каманды init. Гэты флаг стварае екземпляр Prisma Postgres, таму яго трэба не включаць, калі вы викорыстоўваеце сваю сабственную базу дадзеных.

Ёнколі хочаце запусціць базу дадзеных локальна без інсталявання Postgres, Prisma можа запусціць для вас сервер развітку:

npx prisma dev

У кроцы init ўжо быў запісаны DATABASE_URL у вашым файле .env, які паказуе на гэты локальны сервер. Якщо вы вялікі час запусцілі Postgres локальна, або хочаце паказаць на галузь Neon, заменіце гэтыя значэння на вашы сабскайны рэштры з’ўязку. Ёнколі хочаце дакладней пазнаць, як Prisma 7 працуе ў проекте Next.js 16, уключаючы новы файл налашоўкаў, адзірніце нашыя інструкціі па налашоўкі Prisma 7.

Стварэнне прыкладнага дапамогі posts

У рэште нарадчыка Prisma показана стварэнне маленькага блогу з корыстнікамі та паспэслі. Якщо вы дадзеўнае Prisma ў вялікія праект, вы можете перайсці безпосередна да раздзелу з аутэнтыфікацыяй. Аднак значыць працаваць за інструкціямі, таму што пасляэйшые крокі выконваюцца за адказу, што гэтыя моделі вучыненыя.

Спачатку дадзіце моделі ў файл schema.prisma, як описана ў кроку з схемай, а пасля — запрацаваце зменныя сераўіса ў налашчэннях Prisma, як показана ў кроку з dotenv.

Корэкцыя шляху імпорту кліента, які быў створаны

Калі вы запускаеце скрыпт для заполнення базы дадзэных, ён імпортуе матеріялы безпосередна з створанага падкаталогу prisma, што вызывае проблемы. Створаны код выкладзе кліента пад іменем client, таму першая лінія імпорту ў скрыптэ павінна вядомаць да гэтага месца:

import { PrismaClient, Prisma } from "../app/generated/prisma/client";

Алія @/app працуе таксама добра, як і вялікі шлях ../app.

Тая ж самая практыка застосовуецца, калі вы налаштаваеце спяльнага кліента Prisma ў lib/prisma.ts:

import { PrismaClient } from '../app/generated/prisma/client'

У нарадчыку таксама перыходзіць між конвэнціямі src/app і простым app/ з аднаго крока на іншы. Выберыце тую, якая фактычна викорыстоўваецца у вашам проекте: якщо корнем є src/, тады шлях — ../src/app/generated; якщо нея, то ../app/generated. Большасць памылак типу „модуль не знайдзены“ на гэтым этапе выходзяць з сумешанняў гэтых двух падходаў.

Калі кліент будзе налажаны, дадаць сторункі, якія описвае нарадчык: запит галоўнай сторункі, спіс пасунков, сторунку з деталямі пасунку і форму для стварэння пасунков.

Дадаць Auth.js

Есць два сайты дакументацыі для гэтай бібліятэкі: старэйшы сайт NextAuth і новэйшы дакументацыя Auth.js для Next.js. Дакументацыя Auth.js описвае чынную версію і яе трэба выкорыстоўваць.

З’явіце пакет з яго бэт-каналу:

npm install next-auth@beta

Потым запускайце npx auth secret. Ён генеруе адны разлічны AUTH_SECRET, які Auth.js выкарыстоўвае для падпісвання і шифравання токэнаў і кукі, і запісвае яго ў .env.local. Паколькі гэты проект вялікую частку своих зменных вучыць у .env, перакладзіце тамто цэй значэнне і адмахніце .env.local, ўбачыўшы такім чынам аднае джерэла налаштавання. Не включайце гэты файл у контроль версій.

Тепер следуйце кроках установкі. Ключовым элементам ёсць файл auth.ts, які знаходзится ў корні проекту. У яму адзін раз вызываецца NextAuth, і экспортуюцца чатыры функцыі-памагчыкі, якія викорыстоўваюцца рэштой праекту: HTTP-функцыі handlers, signIn, signOut і auth, якія чытаюць текущую сесію:

import NextAuth from "next-auth"

export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [],
})

Функцыя-обрабоўчык маршруту, яка знаходзится пад ./app/api/auth/[...nextauth]/route.ts, проста перэкспортуе функцыі-обрабоўчыкі GET і POST, каб Auth.js могла обслужваць сваія канцэнтры для входу, апавесці і чытання сесіі. У даным фрагменте каментар і экспорт знаходзіцца на адной лініи; у вашым файле паказваце экспорт на адной лініі пасля імпорту:

import { handlers } from "@/auth" // Referring to the auth.ts we just created export const { GET, POST } = handlers

У інструкцыі з установкі таксама ўключаны файл middleware.ts. Next.js згодна перайменаваў панэль прыемніка запытоў у proxy (дакладней у запісе Next.js пра гэтыя змены), таму файл, описаны ў інструкцыі, не падходзіць для ныякога версіі Next.js. Для гэтай наладкі ён не патрэбны, адтуды кожная захіщаная стороніца і дзеянне самыя пераказваюць інформацію пра сесію. Якщо пазней захочаце захавання на рэвэлі запытоў, паспрабуйце прыстосаваць код да стандарту proxy.

Падключэнне Google як прадаўца абліковання

У Google Cloud Console створыце кліента OAuth з типам «Web application». Установіце разрэшаныя месца выконання JavaScript на localhost:3000, а URL для перыявлення — на localhost:3000/api/auth/callback/google, які ўтвараецца путем Auth.js для прадавца Google. Калі будетэ распрацоўваць прылад, дадаце таксама месца выконання у прыметнай средзе і путэй для перыявлення. Зберагучы ID кліента і секрэт, якія дае Google, у файле вашай средзе пад іменамі змэнных, указанымі ў дакументацыі Auth.js, а не ў коде.

Пасля чаго гід з OAuth дадае компонент для входу. Цэта форма, якая выканана за дапамою вбудованай Server Action, якая вызывае signIn:

import { signIn } from "@/auth"

export function SignIn() {
  return (
    <form
      action={async () => {
        "use server"
        await signIn()
      }}
    >
      <button type="submit">Sign in</button>
    </form>
  )
}

Калі signIn() вызываецца без аргументаў, ён перадае корыстніка на вбудованую сторонку, якая паказвае всі настроеныя провайдэры. Калі настроўаны толькі Google, пасыланне "google" прымусвае абйść гэты проміжны экран.

Адпаведны компонент для выхаду з акаунта, які ўзяты з документацыі па калектываванню сесый, мае той самы формат:

import { signOut } from "@/auth"

export function SignOut() {
  return (
    <form
      action={async () => {
        "use server"
        await signOut()
      }}
    >
      <button type="submit">Sign Out</button>
    </form>
  )
}

Размешчанне кнопак у палі навігацыі

У документацыі не паказана, дзе самэўажыцца гэтыя компоненты. Прыродным месцам є палі навігацыі, якае сама ёсць асінхронным серверным компонентам: ён вызывае auth() і атрыбутуе SignOut, калі є корыстнік, а SignIn — інакш.

import Link from "next/link";
import { auth } from "../auth"
import SignIn from "./sign-in";
import SignOut from "./sign-out";

export default async function Navigation() {
    const session = await auth();

    return (
        <>
            <nav className="sticky top-0 z-50 bg-white/80 backdrop-blur-md border-b border-gray-200">
                <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
                    <div className="flex items-center h-16">
                        {/* Logo */}
                        <Link href="/" className="flex items-center space-x-2">
                        <span className="text-xl font-semibold text-[#369b6f]">
                            Prisma + NextAuth + Neon
                        </span>
                        </Link>

                        {/* Spacer */}
                        <div className="flex-1"></div>

                        {/* Navigation Links */}
                        <div className="hidden md:flex items-center mr-8">
                        <Link
                            href="/posts"
                            className="text-gray-700 font-medium hover:text-[#369b6f] transition-all duration-200"
                        >
                            Posts
                        </Link>
                        </div>

                        {/* Auth Buttons */}
                        <div className="flex items-center space-x-2 sm:space-x-4">
                        {session?.user ? (
                            <SignOut/>
                        ) : (
                            <SignIn/>
                        )}
                        </div>
                    </div>
                </div>
            </nav>
        </>
    );
}

Зверніце увагу на імпорты: гэты файл викорыстоўвае стандартныя імпорты для SignIn і SignOut, тады як компаненты вышэй викорыстоўваюць імпорты па імені. Або зменіце імпорты на import { SignIn } from "./sign-in", або выэкспортуйце компаненты як стандартныя; інакша процес будавання збіецца.

Потым адрасуйце навігацыю ў корневай структуре, каб яна выклікалася на кожной сторонцы:

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body>
          <Navigation/>
          <main>
            {children}
          </main>
      </body>
    </html>
  );
}

Не забудзіце імпортуваць Navigation у верхней часты файлу app/layout.tsx. Пасля перзагрузкі сервера разам з кожной сторонай будзе адміністрацыйныя палітра з кнопкамі «Увайсь» і «Выйдзі». Памятайце, што обе кнопкі выкарыстоўваюць вбудованыя Server Actions, таму яны працуюць толькі ўнутры Server Components. Client Component не можа визначаць функціі сервера ўбудована; у такім случыку трэба перашырzyćія ціх дзеянняў у околачны файл, пазначаны "use server", і імпортуваць іх.

Зберагчыце данні пра корыстнікаў за дапамойкай Prisma adapter

На даны момент процес увайсу працюе, але нічога пра корыстніка не зберагваецца ў вашай базе дадзенаў. Prisma adapter спаўнаючы ролю моста, з’яеднвае Auth.js з вашым Prisma кліентам, так што данні пра корыстнікаў, асоціяваныя з акункамі OAuth і сесіямі, запісваюцца ў Postgres. Установіце яго:

npm install @auth/prisma-adapter

Пасуйце auth.ts, каб ён перадаў адаптар вашага спяльванага кліента Prisma і зарэгістраваў прадавца Google:

import NextAuth from "next-auth"
import Google from "next-auth/providers/google"
import { PrismaAdapter } from "@auth/prisma-adapter";
import prisma from "@/lib/prisma";

export const { handlers, signIn, signOut, auth } = NextAuth({
    adapter: PrismaAdapter(prisma),
    providers: [Google],
})

Калі адаптар настроўаны, Auth.js за замовчаннем храніць сесіі ў базе дадзенаў, а не у падпісаным кукі, што дазволяе анулюваць сесіі з сервера, але за цэну прабачання базы дадзенаў з кожным пераглядам сесіі. Прадавец Google аўтаматычна берае сваія кранты з зменных сяродовышча, калі яны адпавядаюць правілам іменавання Auth.js.

Спакоіванне модэлю пользователя

Адаптару патрабуюць спецыяльныя табліцы і столбцы, якіе пераказаны ў схеме адаптара Prisma. Копіюйце іх у schema.prisma. Гэта стварае суперсчытанне з прыкладным дапрацоўкам: у падрабніцы Prisma была створана модель User з савойм типам id, тады калі Auth.js чакае роўнасцю id у вачынку плюс калькі дапаможных полей. Для чыстага прыкладнага проекту самы просты спосаб — заменіць модель і перзаснаваць базу дадзенаў з нуля. Якщо вы інтегруеце ў дапрацоўку, якая вже мае рэальных корыстувачоў, нічога не перазначайце; спланаваць належную міграцыю, якая будзе перакладаць існуючых корыстувачоў у новую структуру. Гэта адна з самастойных проблем, якія не ў межах гэтага карцёнага вядома.

Ёжы перзаснаваць все чыста, спачатку зупініце запуск seed-дадзенаў, адтыму што гэтыя даны аднасаюцца да старай структуры корыстувачоў. Адмахніце елемент seed у prisma.config.ts:

seed: `tsx prisma/seed.ts`

Пасля чаго неабяжна перзначыць базу дадзеных, яка адмахвае ўсія таблыцы і данні:

npx prisma migrate reset

Гэты каскадны запускаваць толькі на базе дадзеных для разработкі. Пасля чаго неабяжна стварыць майграцыю для новага схэмы і застосаваць яе:

npx prisma migrate dev

Зноў запусціце прыкладнэц (npm run dev) і заўісьце праз Google. У таблыцы User должна з’явіцца новая рэч, а ў таблыцы Account — рэч, якая аднасаеся да лінку Google. Чыба праследзіць гэтыя рэчы, запусціце npx prisma studio — візуальны праглядач базы дадзеных, які ўключаецца ў Prisma.

Прыналежнасць пасоў да заўішанага пользователя

Прыклад формы з гіду Prisma стварае паспэкты з жорсткаямуцаным authorId. Тепер, калі існуюць сесіі, сторанка можа прачытаць сесію і выкорыстоўваць рэальны ID пользователя. Наведзеная нижэй сторанка вызывае auth(), паказвае прыемлівку анонімным візітарам і перадае session.user.id да Prisma пад час стварэння паспэкта. Пасля чаго яна перапрацоўвае спіс паспэктов і перыяхоўвае на яго:

export default async function NewPost() {
  const session = await auth();

  async function createPost(formData: FormData) {
    "use server";

    if(!session?.user?.id){
      return;
    }

    const title = formData.get("title") as string;
    const content = formData.get("content") as string;

    await prisma.post.create({
      data: {
        title,
        content,
        authorId: session.user.id,
      },
    });

    revalidatePath("/posts");
    redirect("/posts");
  }

  if(!session?.user){
    return <div>You need to sign in to create a post</div>
  }

  ...

}

Є адаптация, яка трэба здзейсніць прытаму, перш чым гэта можа быць выкорыстана ў працоўнай сітцы. Адзін з элементаў коду чытае значэнне session з рэндарування сторынкі чераз клозурę. Server Action — это публічны канцэнтр пункт, які можна выклікаць безпасабліва, таму болей безпечны спосаб — вызваць auth() у тэле адзінкі і пераканацца ў рэзультате там, так што перакананне будзе выконвалася ў кожны раз, калі надаецца даны. Мы пояснюем прычыны ў чаму Server Actions патрабуюць автарызаціі ў тэле кожнай функцыі. Таксама ўжоць каласці — пераканацца ў правамернасці значэння title і content, а не прыменяць кастаўванне as string.

Заключэнне

  • Следавайце офіцыйным адказам Prisma і Auth.js, але скорэктавайце імпорт кліента, каб ён вялічыў generated/prisma/client, і будзьце адносовы ў выкарыстоўванні src/app і app/.
  • Зберагаўце всі секрэты і стрынгі з’яносу ў адным файле сэрвіса, які ніколі не будзе дадзены да комітавання.
  • Праскочыце документаваны файл middleware.ts; ныякі Next.js вядома выкарыстоўвае пратаку проксі, і тут дастатнекія ўпыны auth() на рэвэле.
  • Дадаўшы адаптар Prisma, змінюецца формат User, які ўпэўнены. Для дамаўкі годзіцца перазначыць яго, але для існуючых дадзеных трэба рэальная міграцыя.
  • Пераканаўцеся ў стане сэсіі ў кожной Server Action, а не толькі на сторонцы, якая выводзіць форму.

Далейшыя ежы — это вход праз кліентскія компаненты і настройка DATABASE_URL на хоставануюеся інстанцыю Postgres для развяртання.