Auth0 w Next.js - przewodnik po implementacji (z App Routerem)

W tym przewodniku krok po kroku zintegrujemy Auth0 z aplikacją w Next.js, korzystając z tego frameworka do budowy aplikacji renderowanych po stronie serwera. Auth0 to elastyczna i bezpieczna platforma do uwierzytelniania i autoryzacji, a jej integracja z Next.js pozwala budować bezpieczne, skalowalne i przyjazne użytkownikom aplikacje webowe.

Wymagania wstępne

Zanim zaczniesz, upewnij się, że masz:

  • Podstawową znajomość Next.js.
  • Konto Auth0. Jeśli jeszcze go nie masz, załóż je za darmo.
  • Node.js zainstalowany na swoim komputerze.

Krok 1: Konfiguracja konta Auth0

  1. Utwórz nową aplikację w Auth0: Zaloguj się do swojego panelu Auth0, przejdź do Applications > Applications i kliknij "Create Application."
  2. Wybierz Regular Web Application i przejdź dalej.
  3. Skonfiguruj ustawienia aplikacji: W polach "Allowed Callback URLs," "Allowed Logout URLs," i "Allowed Web Origins" podaj adresy swojego środowiska deweloperskiego i produkcyjnego.

Krok 2: Instalacja Auth0 SDK dla Next.js

Auth0 udostępnia SDK stworzone specjalnie z myślą o aplikacjach w Next.js. Zainstaluj je za pomocą npm albo yarn:

scss
npm install @auth0/nextjs-auth0

Krok 3: Konfiguracja Auth0 SDK

Skonfiguruj SDK: utwórz plik .env.local w głównym katalogu projektu i dodaj do niego swoje dane uwierzytelniające Auth0 na podstawie ustawień projektu z panelu aplikacji Auth0

Ustawienia Auth0
Ustawienia Auth0

Aby wygenerować sekret, użyj polecenia:

auth0 secretbash
node -e "console.log(crypto.randomBytes(32).toString('hex'))"
bash
AUTH0_SECRET='YOUR_SECRET'
AUTH0_BASE_URL='http://localhost:3000'
AUTH0_ISSUER_BASE_URL='https://YOUR_DOMAIN.auth0.com'
AUTH0_CLIENT_ID='YOUR_CLIENT_ID'
AUTH0_CLIENT_SECRET='YOUR_CLIENT_SECRET'

Zastąp placeholdery swoją faktyczną domeną Auth0 i danymi klienta.

Utwórz plik route.ts w katalogu src/app/api/auth/[auth0]. Ten plik będzie obsługiwał adresy logowania, wylogowania i callbacku:

typescript
import { handleAuth } from '@auth0/nextjs-auth0';

export default handleAuth();

Następnie owiń komponent RootLayout komponentem UserProvider

typescript
import Navbar from "@/components/NavBar";
import { UserProvider } from "@auth0/nextjs-auth0/client";
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

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

Krok 4: Uwierzytelnianie za pomocą Auth0

Aby dodać logowanie i wylogowanie, użyj hooków i komponentów Auth0 dostarczanych przez SDK. Utwórz na przykład plik components/NavBar.js:

typescript
"use client";
import { useUser } from "@auth0/nextjs-auth0/client";
import Link from "next/link";

const Navbar = () => {
  const { user, isLoading } = useUser();

  return (
    <nav className="bg-gray-800 text-white p-4">
      <div className="container mx-auto flex justify-between items-center">
        <div className="text-lg font-semibold">MyApp</div>
        <div>
          {user && (
            <Link
              href="/api/auth/logout"
              className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded"
            >
              Sign Out
            </Link>
          )}
          {!user && !isLoading && (
            <Link
              href="/api/auth/login?returnTo=%2Fprofile"
              className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
            >
              Login
            </Link>
          )}
        </div>
      </div>
    </nav>
  );
};

export default Navbar;

Linki uwierzytelniania:

  • Sign Out: Gdy użytkownik jest uwierzytelniony (obiekt user istnieje), wyświetla się link "Sign Out". Kliknięcie go przekierowuje użytkownika na /api/auth/logout, co uruchamia proces wylogowania w Auth0. Link jest ostylowany w Tailwind CSS, żeby się wyróżniał i reagował na najechanie kursorem, sygnalizując, że można go kliknąć.
  • Login: Jeśli nie ma uwierzytelnionego użytkownika (user jest falsy), a komponent nie jest w stanie ładowania (isLoading jest falsy), pokazuje się link "Login". Kieruje on użytkownika na /api/auth/login?returnTo=%2Fprofile, gdzie uwierzytelnianiem zajmuje się Auth0, a po udanym logowaniu użytkownik wraca na stronę /profile. Link logowania też jest ostylowany, żeby zachować spójny wygląd i interaktywność.

Jeśli wszystko skonfigurujesz poprawnie, po kliknięciu przycisku logowania zobaczysz formularz logowania:

Krok 5: Ochrona tras

Aby zabezpieczyć stronę lub trasę API, użyj funkcji wyższego rzędu withPageAuthRequired dla stron i withApiAuthRequired dla tras API. Dostęp będą mieli tylko zalogowani użytkownicy. Niezalogowany użytkownik zostanie przekierowany na stronę logowania.

Renderowanie po stronie klienta

typescript
"use client";

import { withPageAuthRequired } from "@auth0/nextjs-auth0/client";

export default withPageAuthRequired(function CSRPage() {
  return (
    <>
      <div className="mb-5">
        <h1>Client-side Rendered Page</h1>
        <div>
          Protected client-side rendered page. Only logged in users can access
        </div>
      </div>
    </>
  );
});

Strona profilu po stronie klienta

Do pobrania stanu bieżącego użytkownika używam useUser z @auth0/nextjs-auth0.

typescript
"use client";

import { useUser, withPageAuthRequired } from "@auth0/nextjs-auth0/client";

import Alert from "@/components/Alert";
import Highlight from "@/components/Highlight";
import Loading from "@/components/Loading";
import Image from "next/image";

function Profile() {
  const { user, isLoading } = useUser();

  return (
    <>
      {isLoading && <Loading />}
      {user && (
        <>
          <div>
            <Image
              src={user.picture || "https://via.placeholder.com/200"}
              alt={user.name || "profile picture"}
              width={200}
              height={200}
            />
            <h2>{user.name}</h2>
            <p>{user.email}</p>
          </div>

          <Highlight>{JSON.stringify(user, null, 2)}</Highlight>
        </>
      )}
    </>
  );
}

export default withPageAuthRequired(Profile, {
  onRedirecting: () => <Loading />,
  onError: (error) => <Alert>{error.message}</Alert>,
});

Obsługa przekierowania przez onRedirecting

Gdy użytkownik wchodzi na stronę wymagającą uwierzytelnienia, przez krótką chwilę ustalany jest jego status, zanim ewentualnie trafi na stronę logowania. Opcja onRedirecting pozwala określić własne zachowanie albo wyświetlić konkretny komponent na ten czas. To bardzo przydatne dla płynności działania aplikacji, bo możesz pokazać wskaźnik ładowania albo komunikat informujący, że trwa uwierzytelnianie.

Możesz na przykład użyć onRedirecting, by pokazać spinner albo komunikat "Proszę czekać". Taka natychmiastowa informacja zwrotna zapobiega dezorientacji i upewnia użytkownika, że aplikacja przetwarza jego żądanie, a to poprawia ogólne wrażenie responsywności.

Eleganckie obsługiwanie błędów przez onError

Mimo najlepszych starań podczas uwierzytelniania mogą pojawić się błędy. Ich przyczyną bywają problemy z siecią, błędy konfiguracji albo wygasłe sesje. Opcja onError to narzędzie do eleganckiej obsługi takich sytuacji. Pozwala określić, jak zareagować na błędy uwierzytelniania: wyświetlić komunikat użytkownikowi, przekierować go na stronę błędu albo zalogować błąd do dalszej analizy.

Przemyślana strategia onError sprawia, że użytkownicy nie zostają bez pomocy, gdy coś pójdzie nie tak. Zamiast tego dostają jasną informację o problemie i, o ile to możliwe, wskazówki, jak go rozwiązać lub co zrobić dalej.

Renderowanie po stronie serwera

typescript
import { getSession, withPageAuthRequired } from "@auth0/nextjs-auth0";

import Highlight from "@/components/Highlight";

export default withPageAuthRequired(
  async function SSRPage() {
    const session = await getSession();
    const user = session?.user;
    return (
      <>
        <div className="mb-5">
          <h1>Server-side Rendered Page</h1>
          <div>
            Protected server-side rendered page. Only logged in users can access
          </div>
        </div>
        <h6>User</h6>
        <Highlight>{JSON.stringify(user, null, 2)}</Highlight>
      </>
    );
  },
  { returnTo: "/ssr" }
);

Dostosowanie przekierowania: Funkcja withPageAuthRequired jest skonfigurowana opcją ({ returnTo: "/ssr" }), która określa adres, na jaki użytkownik trafi po zalogowaniu. Dzięki temu po uwierzytelnieniu wraca na stronę, na którą chciał wejść, co poprawia wygodę korzystania z aplikacji.

przykładowy kod: github Auth0-example

Podsumowanie

Udało Ci się zintegrować Auth0 z aplikacją w Next.js, co daje bezpieczne uwierzytelnianie i ochronę tras. Taka konfiguracja zapewnia solidny mechanizm uwierzytelniania, który poprawia bezpieczeństwo i wygodę korzystania z Twojej aplikacji.

Więcej szczegółów i zaawansowanych konfiguracji znajdziesz w dokumentacji Auth0.

Miłego kodowania!