paint-brush
Verwenden der Firebase-Authentifizierung mit den neuesten Next.js-Funktionenvon@awinogrodzki
26,729 Lesungen
26,729 Lesungen

Verwenden der Firebase-Authentifizierung mit den neuesten Next.js-Funktionen

von Amadeusz Winogrodzki32m2024/04/04
Read on Terminal Reader

Zu lang; Lesen

Eine umfassende Schritt-für-Schritt-Anleitung zur Integration von Firebase Authentication mit Next.js unter Verwendung der Bibliothek `next-firebase-auth-edge` mit Null-Bundle-Größe. Sie enthält Schritte für Benutzerregistrierung, Anmelde- und Abmeldefunktionen sowie Umleitungslogik für ein nahtloses Benutzererlebnis. In dieser Anleitung erfahren Sie, wie Sie Firebase Authentication mit den neuesten Next.js-Funktionen wie App Router, Middleware und Serverkomponenten integrieren. Sie endet mit Anweisungen zum Bereitstellen der App auf Vercel und zeigt die Benutzerfreundlichkeit und das zukunftssichere Design der Bibliothek für Entwickler, die ihre Next.js-Anwendungen mit Firebase Authentication verbessern möchten.
featured image - Verwenden der Firebase-Authentifizierung mit den neuesten Next.js-Funktionen
Amadeusz Winogrodzki HackerNoon profile picture
0-item
1-item

Einführung in next-firebase-auth-edge

Sie haben diesen Artikel wahrscheinlich gefunden, als Sie nach Möglichkeiten gesucht haben, die Firebase-Authentifizierung zu Ihrer bestehenden oder neuen Next.js-Anwendung hinzuzufügen. Ihr Ziel ist es, eine kluge, unvoreingenommene und zukunftsorientierte Entscheidung zu treffen, die die Erfolgschancen Ihrer App maximiert. Als Schöpfer von next-firebase-auth-edge muss ich zugeben, dass es nicht meine Stärke ist, eine völlig unvoreingenommene Meinung abzugeben, aber ich werde zumindest versuchen, den Ansatz zu rechtfertigen, den ich beim Entwurf der Bibliothek gewählt habe. Wir hoffen, dass Sie am Ende dieses Leitfadens feststellen werden, dass der Ansatz sowohl einfach als auch langfristig praktikabel ist.


Wie es begann

Ich erspare Ihnen lange Einführungen. Lassen Sie mich nur sagen, dass die Idee für die Bibliothek von einer Situation inspiriert wurde, die möglicherweise der Ihren ähnelt. Es war die Zeit, als Next.js eine Canary-Version von App Router veröffentlichte. Ich habe an einer App gearbeitet, die stark auf Umschreibungen und interne Weiterleitungen angewiesen war. Dafür verwendeten wir den benutzerdefinierten Node.js express -Server, der die Next.js-App rendert.


Wir waren von den App Router- und Serverkomponenten sehr begeistert, waren uns jedoch bewusst, dass sie nicht mit unserem benutzerdefinierten Server kompatibel sein würden. Middleware schien eine leistungsstarke Funktion zu sein, die wir nutzen konnten, um die Notwendigkeit eines benutzerdefinierten Express-Servers zu eliminieren und uns stattdessen ausschließlich auf die integrierten Funktionen von Next.js zu verlassen, um Benutzer dynamisch auf verschiedene Seiten umzuleiten und umzuschreiben.


Dieses Mal verwendeten wir next-firebase-auth . Die Bibliothek hat uns sehr gut gefallen, aber sie hat unsere Authentifizierungslogik über next.config.js , pages/_app.tsx , pages/api/login.ts und pages/api/logout.ts verbreitet, die als Legacy gelten würden früh genug. Außerdem war die Bibliothek nicht mit der Middleware kompatibel, was uns daran hinderte, URLs neu zu schreiben oder Benutzer basierend auf ihrem Kontext umzuleiten.


Also begann ich mit der Suche, fand aber zu meiner Überraschung keine Bibliothek, die die Firebase-Authentifizierung innerhalb der Middleware unterstützte. – Warum könnte das sein? Es ist unmöglich! Als Softwareentwickler mit mehr als 11 Jahren kommerzieller Erfahrung in Node.js und React bereitete ich mich darauf vor, dieses Rätsel anzugehen.


Also fing ich an. Und die Antwort wurde offensichtlich. Middleware wird in Edge Runtime ausgeführt. In Edge Runtime ist keine Firebase-Bibliothek verfügbar, die mit Web-Crypto-APIs kompatibel ist. Ich war dem Untergang geweiht . Ich fühlte mich hilflos. Ist dies das erste Mal, dass ich tatsächlich warten muss, um mit den neuen und ausgefallenen APIs spielen zu können? - Nein. Ein bewachter Topf kocht niemals ueber. Ich hörte schnell auf zu schluchzen und begann , next-firebase-auth , firebase-admin und mehrere andere JWT-Authentifizierungsbibliotheken zurückzuentwickeln und sie an die Edge Runtime anzupassen. Ich nutzte die Gelegenheit, um alle Probleme anzugehen, auf die ich bei früheren Authentifizierungsbibliotheken gestoßen war, mit dem Ziel, die leichteste, am einfachsten zu konfigurierende und zukunftsorientierte Authentifizierungsbibliothek zu erstellen.


Etwa zwei Wochen später wurde Version 0.0.1 von next-firebase-auth-edge geboren. Es war ein solider Proof of Concept, aber Sie würden Version 0.0.1 nicht verwenden wollen. Vertrau mir.


Wie es läuft

Fast zwei Jahre später freue ich mich, Ihnen mitteilen zu können, dass die Bibliothek nach 372 Commits , 110 gelösten Problemen und einer Menge unschätzbarem Feedback von großartigen Entwicklern aus aller Welt ein Stadium erreicht hat, in dem meine anderen Knoten mir zustimmend zustimmen.



Mein anderes Ich



In dieser Anleitung werde ich Version 1.4.1 von next-firebase-auth-edge verwenden, um eine authentifizierte Next.js-App von Grund auf zu erstellen. Wir werden jeden Schritt im Detail durchgehen, beginnend mit der Erstellung eines neuen Firebase-Projekts und einer Next.js-App, gefolgt von der Integration mit den Bibliotheken next-firebase-auth-edge und firebase/auth . Am Ende dieses Tutorials werden wir die App auf Vercel bereitstellen, um zu bestätigen, dass alles sowohl lokal als auch in der produktionsbereiten Umgebung funktioniert.


Firebase einrichten

In diesem Teil wird davon ausgegangen, dass Sie die Firebase-Authentifizierung noch nicht eingerichtet haben. Andernfalls können Sie gerne mit dem nächsten Teil fortfahren.


Gehen wir zur Firebase Console und erstellen ein Projekt


Nachdem das Projekt erstellt wurde, aktivieren wir die Firebase-Authentifizierung. Öffnen Sie die Konsole, gehen Sie zu Erstellen > Authentifizierung > Anmeldemethode und aktivieren Sie die E-Mail- und Kennwortmethode . Das ist die Methode, die wir in unserer App unterstützen werden


E-Mail-/Passwort-Anmeldemethode aktivieren


Nachdem Sie Ihre erste Anmeldemethode aktiviert haben, sollte die Firebase-Authentifizierung für Ihr Projekt aktiviert sein und Sie können Ihren Web-API-Schlüssel in den Projekteinstellungen abrufen


Web-API-Schlüssel abrufen


Kopieren Sie den API-Schlüssel und bewahren Sie ihn sicher auf. Öffnen wir nun die nächste Registerkarte – Cloud Messaging – und notieren Sie die Absender-ID . Wir werden es später brauchen.


Absender-ID abrufen


Zu guter Letzt müssen wir Anmeldeinformationen für das Dienstkonto generieren. Dadurch erhält Ihre App vollen Zugriff auf Ihre Firebase-Dienste. Gehen Sie zu Projekteinstellungen > Dienstkonten und klicken Sie auf Neuen privaten Schlüssel generieren . Dadurch wird eine .json Datei mit den Anmeldeinformationen des Dienstkontos heruntergeladen. Speichern Sie diese Datei an einem bekannten Ort.



Das ist es! Wir sind bereit, die Next.js-App mit der Firebase-Authentifizierung zu integrieren

Erstellen Sie eine Next.js-App von Grund auf

In dieser Anleitung wird davon ausgegangen, dass Sie Node.js und npm installiert haben. Die in diesem Tutorial verwendeten Befehle wurden anhand der neuesten LTS Node.js v20 überprüft. Sie können die Knotenversion überprüfen, indem Sie node -v im Terminal ausführen. Sie können auch Tools wie NVM verwenden, um schnell zwischen Node.js-Versionen zu wechseln.

Einrichten der Next.js-App mit CLI

Öffnen Sie Ihr Lieblingsterminal, navigieren Sie zu Ihrem Projektordner und führen Sie es aus

 npx create-next-app@latest


Um es einfach zu halten, verwenden wir die Standardkonfiguration. Das bedeutet, dass wir TypeScript und tailwind verwenden werden

 ✔ What is your project named? … my-app ✔ Would you like to use TypeScript? … Yes ✔ Would you like to use ESLint? … Yes ✔ Would you like to use Tailwind CSS? … Yes ✔ Would you like to use `src/` directory? … No ✔ Would you like to use App Router? (recommended) … Yes ✔ Would you like to customize the default import alias (@/*)? … No


Navigieren wir zum Stammverzeichnis des Projekts und stellen wir sicher, dass alle Abhängigkeiten installiert sind

 cd my-app npm install


Um zu bestätigen, dass alles wie erwartet funktioniert, starten wir den Next.js-Entwicklungsserver mit dem Befehl npm run dev . Wenn Sie http://localhost:3000 öffnen, sollte die Begrüßungsseite von Next.j angezeigt werden, die etwa so aussieht:


Willkommensseite von Next.js

Umgebungsvariablen vorbereiten

Bevor wir mit der Integration in Firebase beginnen, benötigen wir eine sichere Möglichkeit zum Speichern und Lesen unserer Firebase-Konfiguration. Glücklicherweise wird Next.js mit integrierter Dotenv- Unterstützung ausgeliefert.


Öffnen Sie Ihren bevorzugten Code-Editor und navigieren Sie zum Projektordner


Erstellen wir die Datei .env.local im Stammverzeichnis des Projekts und füllen sie mit den folgenden Umgebungsvariablen:


 FIREBASE_ADMIN_CLIENT_EMAIL=... FIREBASE_ADMIN_PRIVATE_KEY=... AUTH_COOKIE_NAME=AuthToken AUTH_COOKIE_SIGNATURE_KEY_CURRENT=secret1 AUTH_COOKIE_SIGNATURE_KEY_PREVIOUS=secret2 USE_SECURE_COOKIES=false NEXT_PUBLIC_FIREBASE_PROJECT_ID=... NEXT_PUBLIC_FIREBASE_API_KEY=AIza... NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=....firebaseapp.com NEXT_PUBLIC_FIREBASE_DATABASE_URL=....firebaseio.com NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=...


Bitte beachten Sie, dass Variablen mit dem Präfix NEXT_PUBLIC_ im clientseitigen Bundle verfügbar sind. Wir benötigen diese, um das Firebase Auth Client SDK einzurichten


NEXT_PUBLIC_FIREBASE_PROJECT_ID , FIREBASE_ADMIN_CLIENT_EMAIL und FIREBASE_ADMIN_PRIVATE_KEY können aus .json Datei abgerufen werden, die nach dem Generieren der Anmeldeinformationen für das Dienstkonto heruntergeladen wurde


AUTH_COOKIE_NAME ist der Name des Cookies, das zum Speichern von Benutzeranmeldeinformationen verwendet wird

AUTH_COOKIE_SIGNATURE_KEY_CURRENT und AUTH_COOKIE_SIGNATURE_KEY_PREVIOUS sind Geheimnisse, mit denen wir die Anmeldeinformationen signieren werden


NEXT_PUBLIC_FIREBASE_API_KEY ist ein Web-API-Schlüssel , der von der allgemeinen Seite „Projekteinstellungen“ abgerufen wird

NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN ist Ihre Projekt-ID .firebaseapp.com

NEXT_PUBLIC_FIREBASE_DATABASE_URL ist Ihre Projekt-ID .firebaseio.com

NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID kann auf der Seite „Projekteinstellungen“ > „Cloud Messaging“ abgerufen werden


USE_SECURE_COOKIES werden nicht für die lokale Entwicklung verwendet, sind aber nützlich, wenn wir unsere App in Vercel bereitstellen

Integration mit Firebase-Authentifizierung

Installation von next-firebase-auth-edge und Erstkonfiguration

Fügen Sie die Bibliothek zu den Abhängigkeiten des Projekts hinzu, indem Sie npm install next-firebase-auth-edge@^1.4.1 ausführen


Lassen Sie uns die Datei config.ts erstellen, um unsere Projektkonfiguration zu kapseln. Dies ist nicht erforderlich, erleichtert aber die Lesbarkeit der Codebeispiele.

Verbringen Sie nicht zu viel Zeit damit, über diese Werte nachzudenken. Wir werden sie im weiteren Verlauf detaillierter erläutern.


 export const serverConfig = { cookieName: process.env.AUTH_COOKIE_NAME!, cookieSignatureKeys: [process.env.AUTH_COOKIE_SIGNATURE_KEY_CURRENT!, process.env.AUTH_COOKIE_SIGNATURE_KEY_PREVIOUS!], cookieSerializeOptions: { path: "/", httpOnly: true, secure: process.env.USE_SECURE_COOKIES === "true", sameSite: "lax" as const, maxAge: 12 * 60 * 60 * 24, }, serviceAccount: { projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID!, clientEmail: process.env.FIREBASE_ADMIN_CLIENT_EMAIL!, privateKey: process.env.FIREBASE_ADMIN_PRIVATE_KEY?.replace(/\\n/g, "\n")!, } }; export const clientConfig = { projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID, apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY!, authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, databaseURL: process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL, messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID };


Middleware hinzufügen

Erstellen Sie die Datei middleware.ts im Stammverzeichnis des Projekts und fügen Sie Folgendes ein

 import { NextRequest } from "next/server"; import { authMiddleware } from "next-firebase-auth-edge"; import { clientConfig, serverConfig } from "./config"; export async function middleware(request: NextRequest) { return authMiddleware(request, { loginPath: "/api/login", logoutPath: "/api/logout", apiKey: clientConfig.apiKey, cookieName: serverConfig.cookieName, cookieSignatureKeys: serverConfig.cookieSignatureKeys, cookieSerializeOptions: serverConfig.cookieSerializeOptions, serviceAccount: serverConfig.serviceAccount, }); } export const config = { matcher: [ "/", "/((?!_next|api|.*\\.).*)", "/api/login", "/api/logout", ], };


Ob Sie es glauben oder nicht, wir haben gerade den Server unserer App in die Firebase-Authentifizierung integriert. Bevor wir es tatsächlich verwenden, erklären wir die Konfiguration ein wenig:


loginPath weist authMiddleware an, den Endpunkt GET /api/login verfügbar zu machen. Wenn dieser Endpunkt mit dem Header Authorization: Bearer ${idToken} *“ aufgerufen wird, antwortet er mit dem Header „HTTP(S)-Only Set-Cookie , der signierte benutzerdefinierte Token und Aktualisierungstoken enthält


* idToken wird mit der Funktion getIdToken abgerufen, die im Firebase Client SDK verfügbar ist. Mehr dazu später.


In ähnlicher Weise weist logoutPath die Middleware an, GET /api/logout verfügbar zu machen, erfordert jedoch keine zusätzlichen Header. Beim Aufruf werden Authentifizierungscookies aus dem Browser entfernt.


apiKey ist ein Web-API-Schlüssel. Middleware verwendet es, um benutzerdefinierte Token zu aktualisieren und Authentifizierungscookies zurückzusetzen, nachdem die Anmeldeinformationen abgelaufen sind.


cookieName ist der Name des Cookies, das von den Endpunkten /api/login und /api/logout gesetzt und entfernt wird


cookieSignatureKeys ist eine Liste geheimer Schlüssel, mit denen Benutzeranmeldeinformationen signiert werden. Anmeldeinformationen werden immer mit dem ersten Schlüssel in der Liste signiert, daher müssen Sie mindestens einen Wert angeben. Sie können mehrere Schlüssel bereitstellen, um eine Schlüsselrotation durchzuführen


cookieSerializeOptions sind Optionen, die beim Generieren Set-Cookie Headers an Cookie übergeben werden. Weitere Informationen finden Sie in der Cookie- README-Datei


serviceAccount autorisiert die Bibliothek, Ihre Firebase-Dienste zu nutzen.


Der Matcher weist den Next.js-Server an, Middleware für /api/login , /api/logout , / und jeden anderen Pfad auszuführen, bei dem es sich nicht um eine Datei oder einen API-Aufruf handelt.

 export const config = { matcher: [ "/", "/((?!_next|api|.*\\.).*)", "/api/login", "/api/logout", ], };


Sie fragen sich vielleicht, warum wir Middleware nicht für alle /api/* -Aufrufe aktivieren. Das wäre möglich, aber es empfiehlt sich, nicht authentifizierte Aufrufe innerhalb des API-Route-Handlers selbst zu verarbeiten. Dies würde den Rahmen dieses Tutorials etwas sprengen, aber wenn Sie interessiert sind, lassen Sie es mich wissen und ich bereite einige Beispiele vor!



Wie Sie sehen, ist die Konfiguration minimal und hat einen klar definierten Zweck. Beginnen wir nun mit dem Aufruf unserer Endpunkte /api/login und /api/logout .


Erstellen einer sicheren Homepage

Um die Sache so einfach wie möglich zu gestalten, löschen wir die Standard-Startseite von Next.js und ersetzen sie durch einige personalisierte Inhalte


Öffnen Sie ./app/page.tsx und fügen Sie Folgendes ein:

 import { getTokens } from "next-firebase-auth-edge"; import { cookies } from "next/headers"; import { notFound } from "next/navigation"; import { clientConfig, serverConfig } from "../config"; export default async function Home() { const tokens = await getTokens(cookies(), { apiKey: clientConfig.apiKey, cookieName: serverConfig.cookieName, cookieSignatureKeys: serverConfig.cookieSignatureKeys, serviceAccount: serverConfig.serviceAccount, }); if (!tokens) { notFound(); } return ( <main className="flex min-h-screen flex-col items-center justify-center p-24"> <h1 className="text-xl mb-4">Super secure home page</h1> <p> Only <strong>{tokens?.decodedToken.email}</strong> holds the magic key to this kingdom! </p> </main> ); }


Lassen Sie uns das Stück für Stück aufschlüsseln.


getTokens Funktion dient zum Validieren und Extrahieren von Benutzeranmeldeinformationen aus Cookies

 const tokens = await getTokens(cookies(), { apiKey: clientConfig.apiKey, cookieName: serverConfig.cookieName, cookieSignatureKeys: serverConfig.cookieSignatureKeys, serviceAccount: serverConfig.serviceAccount, });


Es wird mit null aufgelöst, wenn der Benutzer nicht authentifiziert ist, oder ein Objekt mit zwei Eigenschaften:


token , bei dem es sich um eine idToken- string handelt, mit der Sie API-Anfragen an externe Back-End-Dienste autorisieren können. Dies liegt etwas außerhalb des Rahmens, aber es ist erwähnenswert, dass die Bibliothek eine verteilte Servicearchitektur ermöglicht. Der token ist mit allen offiziellen Firebase-Bibliotheken auf allen Plattformen kompatibel und einsatzbereit.


decodedToken ist, wie der Name schon sagt, eine entschlüsselte Version des token , die alle zur Identifizierung des Benutzers erforderlichen Informationen enthält, einschließlich E-Mail-Adresse, Profilbild und benutzerdefinierten Ansprüchen , was uns außerdem ermöglicht, den Zugriff basierend auf Rollen und Berechtigungen einzuschränken.


Nachdem wir tokens erhalten haben, verwenden wir die Funktion „notFound“ von next/navigation , um sicherzustellen, dass die Seite nur für authentifizierte Benutzer zugänglich ist

 if (!tokens) { notFound(); }


Abschließend rendern wir einige grundlegende, personalisierte Benutzerinhalte

 <main className="flex min-h-screen flex-col items-center justify-center p-24"> <h1 className="text-xl mb-4">Super secure home page</h1> <p> Only <strong>{tokens?.decodedToken.email}</strong> holds the magic key to this kingdom!" </p> </main>


Lassen wir es laufen.

Falls Sie Ihren Entwicklungsserver geschlossen haben, führen Sie einfach npm run dev aus.


Wenn Sie versuchen, auf http://localhost:3000/ zuzugreifen, sollte 404 angezeigt werden: Diese Seite konnte nicht gefunden werden.


Erfolg! Wir haben unsere Geheimnisse vor neugierigen Blicken geschützt!


firebase installieren und Firebase Client SDK initialisieren

Führen Sie npm install firebase im Stammverzeichnis des Projekts aus


Erstellen Sie nach der Installation des Client-SDK die Datei firebase.ts im Stammverzeichnis des Projekts und fügen Sie Folgendes ein

 import { initializeApp } from 'firebase/app'; import { clientConfig } from './config'; export const app = initializeApp(clientConfig);


Dadurch wird das Firebase Client SDK initialisiert und das App-Objekt für Client-Komponenten verfügbar gemacht

Erstellen einer Registrierungsseite

Was nützt eine supersichere Homepage, wenn niemand sie sehen kann? Lassen Sie uns eine einfache Registrierungsseite erstellen, um Menschen Zugang zu unserer App zu ermöglichen.


Lasst uns eine neue, schicke Seite unter ./app/register/page.tsx erstellen


 "use client"; import { FormEvent, useState } from "react"; import Link from "next/link"; import { getAuth, createUserWithEmailAndPassword } from "firebase/auth"; import { app } from "../../firebase"; import { useRouter } from "next/navigation"; export default function Register() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [confirmation, setConfirmation] = useState(""); const [error, setError] = useState(""); const router = useRouter(); async function handleSubmit(event: FormEvent) { event.preventDefault(); setError(""); if (password !== confirmation) { setError("Passwords don't match"); return; } try { await createUserWithEmailAndPassword(getAuth(app), email, password); router.push("/login"); } catch (e) { setError((e as Error).message); } } return ( <main className="flex min-h-screen flex-col items-center justify-center p-8"> <div className="w-full bg-white rounded-lg shadow dark:border md:mt-0 sm:max-w-md xl:p-0 dark:bg-gray-800 dark:border-gray-700"> <div className="p-6 space-y-4 md:space-y-6 sm:p-8"> <h1 className="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white"> Pray tell, who be this gallant soul seeking entry to mine humble abode? </h1> <form onSubmit={handleSubmit} className="space-y-4 md:space-y-6" action="#" > <div> <label htmlFor="email" className="block mb-2 text-sm font-medium text-gray-900 dark:text-white" > Your email </label> <input type="email" name="email" value={email} onChange={(e) => setEmail(e.target.value)} id="email" className="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="[email protected]" required /> </div> <div> <label htmlFor="password" className="block mb-2 text-sm font-medium text-gray-900 dark:text-white" > Password </label> <input type="password" name="password" value={password} onChange={(e) => setPassword(e.target.value)} id="password" placeholder="••••••••" className="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required /> </div> <div> <label htmlFor="confirm-password" className="block mb-2 text-sm font-medium text-gray-900 dark:text-white" > Confirm password </label> <input type="password" name="confirm-password" value={confirmation} onChange={(e) => setConfirmation(e.target.value)} id="confirm-password" placeholder="••••••••" className="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required /> </div> {error && ( <div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert" > <span className="block sm:inline">{error}</span> </div> )} <button type="submit" className="w-full text-white bg-gray-600 hover:bg-gray-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-gray-600 dark:hover:bg-gray-700 dark:focus:ring-primary-800" > Create an account </button> <p className="text-sm font-light text-gray-500 dark:text-gray-400"> Already have an account?{" "} <Link href="/login" className="font-medium text-gray-600 hover:underline dark:text-gray-500" > Login here </Link> </p> </form> </div> </div> </main> ); }


Ich weiß. Es ist viel Text, aber haben Sie Geduld.


Wir beginnen mit "use client"; um anzugeben, dass die Registrierungsseite clientseitige APIs verwendet


 const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [confirmation, setConfirmation] = useState(""); const [error, setError] = useState("");

Dann definieren wir einige Variablen und Setter, um unseren Formularstatus zu halten


 const router = useRouter(); async function handleSubmit(event: FormEvent) { event.preventDefault(); setError(""); if (password !== confirmation) { setError("Passwords don't match"); return; } try { await createUserWithEmailAndPassword(getAuth(app), email, password); router.push("/login"); } catch (e) { setError((e as Error).message); } }

Hier definieren wir unsere Formularübermittlungslogik. Zuerst überprüfen wir, ob password und confirmation gleich sind, andernfalls aktualisieren wir den Fehlerstatus. Wenn die Werte gültig sind, erstellen wir ein Benutzerkonto mit createUserWithEmailAndPassword aus firebase/auth . Wenn dieser Schritt fehlschlägt (z. B. weil die E-Mail angenommen wurde), informieren wir den Benutzer, indem wir den Fehler aktualisieren.


Wenn alles gut geht, leiten wir den Benutzer zur Seite /login weiter. Wahrscheinlich sind Sie im Moment verwirrt, und Sie haben Recht. /login Seite existiert noch nicht. Wir bereiten uns gerade auf das vor, was als nächstes kommt.


Wenn Sie http://localhost:3000/register besuchen, sollte die Seite ungefähr so aussehen:


Registrierungsseite


Erstellen einer Anmeldeseite

Da sich Benutzer nun registrieren können, können sie ihre Identität nachweisen


Erstellen Sie eine Anmeldeseite unter ./app/login/page.tsx


 "use client"; import { FormEvent, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { getAuth, signInWithEmailAndPassword } from "firebase/auth"; import { app } from "../../firebase"; export default function Login() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState(""); const router = useRouter(); async function handleSubmit(event: FormEvent) { event.preventDefault(); setError(""); try { const credential = await signInWithEmailAndPassword( getAuth(app), email, password ); const idToken = await credential.user.getIdToken(); await fetch("/api/login", { headers: { Authorization: `Bearer ${idToken}`, }, }); router.push("/"); } catch (e) { setError((e as Error).message); } } return ( <main className="flex min-h-screen flex-col items-center justify-center p-8"> <div className="w-full bg-white rounded-lg shadow dark:border md:mt-0 sm:max-w-md xl:p-0 dark:bg-gray-800 dark:border-gray-700"> <div className="p-6 space-y-4 md:space-y-6 sm:p-8"> <h1 className="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white"> Speak thy secret word! </h1> <form onSubmit={handleSubmit} className="space-y-4 md:space-y-6" action="#" > <div> <label htmlFor="email" className="block mb-2 text-sm font-medium text-gray-900 dark:text-white" > Your email </label> <input type="email" name="email" value={email} onChange={(e) => setEmail(e.target.value)} id="email" className="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="[email protected]" required /> </div> <div> <label htmlFor="password" className="block mb-2 text-sm font-medium text-gray-900 dark:text-white" > Password </label> <input type="password" name="password" value={password} onChange={(e) => setPassword(e.target.value)} id="password" placeholder="••••••••" className="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required /> </div> {error && ( <div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert" > <span className="block sm:inline">{error}</span> </div> )} <button type="submit" className="w-full text-white bg-gray-600 hover:bg-gray-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-gray-600 dark:hover:bg-gray-700 dark:focus:ring-primary-800" > Enter </button> <p className="text-sm font-light text-gray-500 dark:text-gray-400"> Don&apos;t have an account?{" "} <Link href="/register" className="font-medium text-gray-600 hover:underline dark:text-gray-500" > Register here </Link> </p> </form> </div> </div> </main> ); }


Wie Sie sehen, ist es der Registrierungsseite ziemlich ähnlich. Konzentrieren wir uns auf das Entscheidende:

 async function handleSubmit(event: FormEvent) { event.preventDefault(); setError(""); try { const credential = await signInWithEmailAndPassword( getAuth(app), email, password ); const idToken = await credential.user.getIdToken(); await fetch("/api/login", { headers: { Authorization: `Bearer ${idToken}`, }, }); router.push("/"); } catch (e) { setError((e as Error).message); } }


Hier geschieht die ganze Magie . Wir verwenden signInEmailAndPassword von firebase/auth um idToken des Benutzers abzurufen.


Dann rufen wir den von der Middleware bereitgestellten /api/login Endpunkt auf. Dieser Endpunkt aktualisiert unsere Browser-Cookies mit Benutzeranmeldeinformationen.


Schließlich leiten wir den Benutzer auf die Startseite um, indem wir router.push("/"); aufrufen.


Die Anmeldeseite sollte ungefähr so aussehen



Lass es uns testen!


Gehen Sie zu http://localhost:3000/register und geben Sie eine zufällige E-Mail-Adresse und ein Passwort ein, um ein Konto zu erstellen. Verwenden Sie diese Anmeldeinformationen auf der Seite http://localhost:3000/login . Nachdem Sie auf die Eingabetaste geklickt haben, sollten Sie zur supersicheren Startseite weitergeleitet werden


Super sichere Homepage




Endlich müssen wir unsere eigene , persönliche , extrem sichere Homepage sehen! Aber warte! Wie kommen wir raus?


Wir müssen eine Abmeldeschaltfläche hinzufügen, um uns nicht für immer (oder 12 Tage) von der Welt auszuschließen.


Bevor wir beginnen, müssen wir eine Client-Komponente erstellen, die uns mithilfe des Firebase Client SDK abmelden kann.


Erstellen wir eine neue Datei unter ./app/HomePage.tsx

 "use client"; import { useRouter } from "next/navigation"; import { getAuth, signOut } from "firebase/auth"; import { app } from "../firebase"; interface HomePageProps { email?: string; } export default function HomePage({ email }: HomePageProps) { const router = useRouter(); async function handleLogout() { await signOut(getAuth(app)); await fetch("/api/logout"); router.push("/login"); } return ( <main className="flex min-h-screen flex-col items-center justify-center p-24"> <h1 className="text-xl mb-4">Super secure home page</h1> <p className="mb-8"> Only <strong>{email}</strong> holds the magic key to this kingdom! </p> <button onClick={handleLogout} className="text-white bg-gray-600 hover:bg-gray-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-gray-600 dark:hover:bg-gray-700 dark:focus:ring-primary-800" > Logout </button> </main> ); }


Wie Sie vielleicht bemerkt haben, handelt es sich hierbei um eine leicht modifizierte Version unserer ./app/page.tsx . Wir mussten eine separate Client-Komponente erstellen, da getTokens nur innerhalb von Serverkomponenten und API-Routenhandlern funktioniert, während signOut und useRouter im Client-Kontext ausgeführt werden müssen. Ein bisschen kompliziert, ich weiß, aber es ist tatsächlich ziemlich mächtig. Ich erkläre es später.


Konzentrieren wir uns auf den Abmeldevorgang

 const router = useRouter(); async function handleLogout() { await signOut(getAuth(app)); await fetch("/api/logout"); router.push("/login"); }


Zuerst melden wir uns vom Firebase Client SDK ab. Dann rufen wir den von der Middleware bereitgestellten /api/logout Endpunkt auf. Zum Abschluss leiten wir den Benutzer zur Seite /login weiter.


Lassen Sie uns unsere Server-Homepage aktualisieren. Gehen Sie zu ./app/page.tsx und fügen Sie Folgendes ein

 import { getTokens } from "next-firebase-auth-edge"; import { cookies } from "next/headers"; import { notFound } from "next/navigation"; import { clientConfig, serverConfig } from "../config"; import HomePage from "./HomePage"; export default async function Home() { const tokens = await getTokens(cookies(), { apiKey: clientConfig.apiKey, cookieName: serverConfig.cookieName, cookieSignatureKeys: serverConfig.cookieSignatureKeys, serviceAccount: serverConfig.serviceAccount, }); if (!tokens) { notFound(); } return <HomePage email={tokens?.decodedToken.email} />; }


Jetzt ist unsere Home Server-Komponente nur für das Abrufen von Benutzer-Tokens und deren Weitergabe an HomePage Client-Komponente verantwortlich. Dies ist tatsächlich ein ziemlich häufiges und nützliches Muster.


Lass uns das testen:


Voila! Wir können uns nun nach Belieben bei der Anwendung an- und abmelden. Das ist großartig!

Oder ist es?


Wenn ein nicht authentifizierter Benutzer versucht, die Startseite durch Öffnen von http://localhost:3000/ aufzurufen, wird 404 angezeigt: Diese Seite konnte nicht gefunden werden.

Außerdem können authentifizierte Benutzer weiterhin auf die Seiten http://localhost:3000/register und http://localhost:3000/login zugreifen, ohne sich abmelden zu müssen.


Wir können es besser machen.


Es scheint, dass wir eine Umleitungslogik hinzufügen müssen. Lassen Sie uns einige Regeln definieren:

  • Wenn ein authentifizierter Benutzer versucht, auf die Seiten /register und /login zuzugreifen, sollten wir ihn zu / umleiten.
  • Wenn ein nicht authentifizierter Benutzer versucht, auf die Seite / zuzugreifen, sollten wir ihn zu /login umleiten


Middleware ist eine der besten Möglichkeiten, Weiterleitungen in Next.js-Apps zu verarbeiten. Glücklicherweise unterstützt authMiddleware eine Reihe von Optionen und Hilfsfunktionen, um eine Vielzahl von Umleitungsszenarien zu bewältigen.


Öffnen wir die Datei middleware.ts und fügen diese aktualisierte Version ein

 import { NextRequest, NextResponse } from "next/server"; import { authMiddleware, redirectToHome, redirectToLogin } from "next-firebase-auth-edge"; import { clientConfig, serverConfig } from "./config"; const PUBLIC_PATHS = ['/register', '/login']; export async function middleware(request: NextRequest) { return authMiddleware(request, { loginPath: "/api/login", logoutPath: "/api/logout", apiKey: clientConfig.apiKey, cookieName: serverConfig.cookieName, cookieSignatureKeys: serverConfig.cookieSignatureKeys, cookieSerializeOptions: serverConfig.cookieSerializeOptions, serviceAccount: serverConfig.serviceAccount, handleValidToken: async ({token, decodedToken}, headers) => { if (PUBLIC_PATHS.includes(request.nextUrl.pathname)) { return redirectToHome(request); } return NextResponse.next({ request: { headers } }); }, handleInvalidToken: async (reason) => { console.info('Missing or malformed credentials', {reason}); return redirectToLogin(request, { path: '/login', publicPaths: PUBLIC_PATHS }); }, handleError: async (error) => { console.error('Unhandled authentication error', {error}); return redirectToLogin(request, { path: '/login', publicPaths: PUBLIC_PATHS }); } }); } export const config = { matcher: [ "/", "/((?!_next|api|.*\\.).*)", "/api/login", "/api/logout", ], };


Das sollte es sein. Wir haben alle Weiterleitungsregeln implementiert. Lassen Sie uns das aufschlüsseln.


 const PUBLIC_PATHS = ['/register', '/login'];


 handleValidToken: async ({token, decodedToken}, headers) => { if (PUBLIC_PATHS.includes(request.nextUrl.pathname)) { return redirectToHome(request); } return NextResponse.next({ request: { headers } }); },

handleValidToken wird aufgerufen, wenn gültige Benutzeranmeldeinformationen an die Anforderung angehängt sind, d. h. Der Benutzer ist authentifiziert. Es wird mit tokens als erstem und den geänderten Anforderungsheadern als zweitem Argument aufgerufen. Es sollte mit NextResponse aufgelöst werden.


redirectToHome von next-firebase-auth-edge ist eine Hilfsfunktion, die ein Objekt zurückgibt, das zu NextResponse.redirect(new URL(“/“)) vereinfacht werden kann.


Durch Überprüfen von PUBLIC_PATHS.includes(request.nextUrl.pathname) überprüfen wir, ob der authentifizierte Benutzer versucht, auf die Seite /login oder /register zuzugreifen, und leiten in diesem Fall zur Startseite weiter.



 handleInvalidToken: async (reason) => { console.info('Missing or malformed credentials', {reason}); return redirectToLogin(request, { path: '/login', publicPaths: PUBLIC_PATHS }); },


handleInvalidToken wird aufgerufen, wenn etwas Erwartetes passiert. Eines dieser erwarteten Ereignisse ist, dass der Benutzer Ihre App zum ersten Mal sieht, von einem anderen Gerät aus oder nachdem die Anmeldeinformationen abgelaufen sind.


Da wir wissen, dass handleInvalidToken für nicht authentifizierte Benutzer aufgerufen wird, können wir mit der zweiten Regel fortfahren: Wenn nicht authentifizierte Benutzer versuchen, auf die Seite / zuzugreifen , sollten wir sie zu /login umleiten


Da keine andere Bedingung erfüllt werden muss, geben wir einfach das Ergebnis von redirectToLogin zurück, das zu NextResponse.redirect(new URL(“/login”)) vereinfacht werden kann. Es stellt außerdem sicher, dass der Benutzer nicht in eine Umleitungsschleife gerät.


Zuletzt,

 handleError: async (error) => { console.error('Unhandled authentication error', {error}); return redirectToLogin(request, { path: '/login', publicPaths: PUBLIC_PATHS }); }


Im Gegensatz zu handleInvalidToken wird handleError aufgerufen, wenn etwas Unerwartetes* passiert und möglicherweise untersucht werden muss. Eine Liste möglicher Fehler mit Beschreibung finden Sie in der Dokumentation


Im Fehlerfall protokollieren wir dies und leiten den Benutzer sicher zur Anmeldeseite weiter


* handleError kann mit dem Fehler INVALID_ARGUMENT aufgerufen werden, nachdem die öffentlichen Schlüssel von Google aktualisiert wurden.

Dies ist eine Form der Schlüsselrotation und wird erwartet . Weitere Informationen finden Sie in dieser Github-Ausgabe


Nun, das ist es. Endlich.


Melden wir uns von unserer Web-App ab und öffnen Sie http://localhost:3000/ . Wir sollten zur Seite /login weitergeleitet werden.

Melden wir uns erneut an und versuchen, http://localhost:3000/login einzugeben. Wir sollten zur Seite / weitergeleitet werden.


Wir haben nicht nur für ein nahtloses Benutzererlebnis gesorgt. next-firebase-auth-edge ist eine Bibliothek mit Null-Bundle-Größe , die nur auf dem Server der App funktioniert und keinen zusätzlichen clientseitigen Code einführt. Das resultierende Paket ist wirklich minimal . Das nenne ich perfekt.


Unsere App ist jetzt sowohl in den Server- als auch in den Clientkomponenten vollständig in die Firebase-Authentifizierung integriert. Wir sind bereit, das volle Potenzial von Next.js auszuschöpfen!




Den Quellcode der App finden Sie unter next-firebase-auth-edge/examples/next-typescript-minimal


Epilog

In diesem Leitfaden haben wir die Integration der neuen Next.js-App mit der Firebase-Authentifizierung beschrieben.


Obwohl der Artikel recht umfangreich ist, wurden einige wichtige Teile des Authentifizierungsablaufs weggelassen, etwa das Formular zum Zurücksetzen des Passworts oder andere Anmeldemethoden als E-Mail und Passwort.


Wenn Sie an der Bibliothek interessiert sind, können Sie sich eine Vorschau der vollständigen Next-Firebase-Auth-Edge-Starter-Demoseite ansehen.

Es bietet Firestore-Integration , Serveraktionen , App-Check-Unterstützung und mehr


Die Bibliothek bietet eine eigene Dokumentationsseite mit unzähligen Beispielen


Wenn Ihnen der Artikel gefallen hat, würde ich mich freuen, wenn Sie das Next-Firebase-Auth-Edge- Repository erwähnen. Prost! 🎉



Bonus – Bereitstellung der App auf Vercel

In diesem Bonusleitfaden erfahren Sie, wie Sie Ihre Next.js-App in Vercel bereitstellen

Git-Repository erstellen

Um die Bereitstellung auf Vercel durchführen zu können, müssen Sie ein Repository für Ihre neue App erstellen.


Gehen Sie zu https://github.com/ und erstellen Sie ein neues Repository.


create-next-app hat bereits ein lokales Git-Repository für uns initiiert, Sie müssen also nur zum Stammordner Ihres Projekts gehen und Folgendes ausführen:


 git add --all git commit -m "first commit" git branch -M main git remote add origin [email protected]:path-to-your-new-github-repository.git git push -u origin main


Neues Vercel-Projekt hinzufügen

Gehen Sie zu https://vercel.com/ und melden Sie sich mit Ihrem Github-Konto an


Nachdem Sie sich angemeldet haben, gehen Sie zur Übersichtsseite von Vercel und klicken Sie auf Neu hinzufügen > Projekt


Klicken Sie neben dem Github-Repository, das wir gerade erstellt haben, auf Importieren . Noch nicht bereitstellen.


Vor der Bereitstellung müssen wir die Projektkonfiguration bereitstellen. Fügen wir einige Umgebungsvariablen hinzu:

Bereitstellung in Vercel


Denken Sie daran, USE_SECURE_COOKIES auf true zu setzen, da Vercel standardmäßig HTTPS verwendet


Jetzt können wir auf „Bereitstellen“ klicken


Warten Sie ein oder zwei Minuten und Sie sollten mit einer URL wie dieser auf Ihre App zugreifen können: https://next-typescript-minimal-xi.vercel.app/


Erledigt. Ich wette, Sie haben nicht damit gerechnet, dass es so einfach sein würde.




Wenn Ihnen der Leitfaden gefallen hat, würde ich mich freuen, wenn Sie das Next-Firebase-Auth-Edge- Repository erwähnen.


Gerne können Sie mir auch Ihr Feedback in den Kommentaren mitteilen. Prost! 🎉