next-firebase-auth-edge
girişBu makaleyi muhtemelen mevcut veya yeni Next.js uygulamanıza Firebase Kimlik Doğrulaması eklemenin yollarını ararken bulmuşsunuzdur. Uygulamanızın başarılı olma şansını en üst düzeye çıkaracak akıllı, tarafsız ve geleceğe yönelik bir karar vermeyi hedefliyorsunuz. Next-firebase-auth-edge'in yaratıcısı olarak, tamamen tarafsız bir fikir sunmanın benim yeteneğim olmadığını kabul etmeliyim, ancak en azından kütüphaneyi tasarlarken benimsediğim yaklaşımı haklı çıkarmaya çalışacağım. Umarız bu kılavuzun sonunda yaklaşımı hem basit hem de uzun vadede uygulanabilir bulacaksınız.
Sizi uzun tanıtımlardan kurtaracağım. Kütüphane fikrinin muhtemelen sizinkine benzer bir durumdan ilham aldığını söylememe izin verin. Next.js'nin App Router'ın kanarya sürümünü yayınladığı dönemdi. Yeniden yazmalara ve dahili yönlendirmelere büyük ölçüde dayanan bir uygulama üzerinde çalışıyordum. Bunun için özel Node.js express
sunucu oluşturma Next.js uygulamasını kullanıyorduk.
Uygulama Yönlendiricisi ve Sunucu Bileşenleri için gerçekten heyecanlıydık ancak bunların özel sunucumuzla uyumlu olmayacağının da farkındaydık. Ara katman yazılımı, özel bir Express sunucusuna olan ihtiyacı ortadan kaldırmak için kullanabileceğimiz güçlü bir özellik gibi görünüyordu; bunun yerine, kullanıcıları dinamik olarak farklı sayfalara yönlendirmek ve yeniden yazmak için yalnızca Next.js'nin yerleşik özelliklerine güvenmeyi tercih ettik.
O zaman next-firebase-auth kullanıyorduk. Kitaplığı gerçekten beğendik, ancak kimlik doğrulama mantığımızı eski sayılacak olan next.config.js
, pages/_app.tsx
, pages/api/login.ts
, pages/api/logout.ts
dosyaları aracılığıyla yaydı. yakında. Ayrıca kitaplığın ara yazılımla uyumlu olmaması, URL'leri yeniden yazmamızı veya kullanıcıları bağlamlarına göre yeniden yönlendirmemizi engelliyordu.
Böylece aramaya başladım ama şaşırtıcı bir şekilde ara katman yazılımında Firebase Authentication'ı destekleyen bir kitaplık bulamadım. – Neden olabilir? Bu imkansız! Node.js ve React'ta 11 yıldan fazla ticari deneyime sahip bir yazılım mühendisi olarak bu ikilemin üstesinden gelmeye hazırlanıyordum.
Ben de başladım. Ve cevap belli oldu. Ara yazılım Edge Runtime'ın içinde çalışıyor. Edge Runtime içinde Web Crypto API'leriyle uyumlu bir Firebase kitaplığı yoktur. Ben mahkumdum . Kendimi çaresiz hissettim. Yeni ve şık API'lerle oynamak için gerçekten beklemem gerekecek ilk sefer mi bu? - Hayır. İzlenen tencere asla kaynamaz. Ağlamayı hızla bıraktım ve next-firebase-auth , firebase-admin ve diğer birkaç JWT kimlik doğrulama kütüphanesini Edge Runtime'a uyarlayarak tersine mühendislik yapmaya başladım. En hafif, yapılandırılması en kolay ve geleceğe yönelik kimlik doğrulama kitaplığını oluşturmayı hedefleyerek önceki kimlik doğrulama kitaplıklarında karşılaştığım tüm sorunları ele alma fırsatını değerlendirdim.
Yaklaşık iki hafta sonra next-firebase-auth-edge'in 0.0.1
sürümü doğdu. Bu, kavramın sağlam bir kanıtıydı ancak 0.0.1
sürümünü kullanmak istemezsiniz. Güven bana.
Yaklaşık iki yıl sonra , 372 taahhüt , 110 çözülmüş sorun ve dünya çapındaki harika geliştiricilerden gelen çok sayıda paha biçilmez geri bildirimin ardından kütüphanenin, diğer benliğimin onay için bana katıldığı bir aşamaya ulaştığını duyurmaktan heyecan duyuyorum.
Bu kılavuzda, kimliği doğrulanmış Next.js uygulamasını sıfırdan oluşturmak için next-firebase-auth-edge'in 1.4.1 sürümünü kullanacağım. Yeni bir Firebase projesi ve Next.js uygulamasının oluşturulmasıyla başlayarak, ardından next-firebase-auth-edge
ve firebase/auth
kitaplıklarıyla entegrasyonla başlayarak her adımı ayrıntılı olarak ele alacağız. Bu eğitimin sonunda, her şeyin hem yerel olarak hem de üretime hazır ortamda çalıştığını doğrulamak için uygulamayı Vercel'e dağıtacağız.
Bu bölümde Firebase Authentication'ı henüz kurmadığınız varsayılmaktadır. Aksi takdirde bir sonraki bölüme geçmekten çekinmeyin.
Firebase Konsoluna gidip bir proje oluşturalım
Proje oluşturulduktan sonra Firebase Authentication'ı etkinleştirelim. Konsolu açın ve Oluştur > Kimlik Doğrulama > Oturum açma yöntemini takip edin ve E-posta ve şifre yöntemini etkinleştirin. Uygulamamızda destekleyeceğimiz yöntem budur
İlk oturum açma yönteminizi etkinleştirdikten sonra, projeniz için Firebase Kimlik Doğrulaması etkinleştirilmelidir ve Web API Anahtarınızı Proje Ayarları'ndan alabilirsiniz.
API anahtarını kopyalayın ve güvende tutun. Şimdi bir sonraki sekme olan Bulut Mesajlaşma'yı açalım ve Gönderen Kimliğini not edelim. Daha sonra ihtiyacımız olacak.
Son fakat en az değil, hizmet hesabı kimlik bilgilerini oluşturmamız gerekiyor. Bunlar, uygulamanızın Firebase hizmetlerinize tam erişim sağlamasına olanak tanır. Proje Ayarları > Hizmet hesapları'na gidin ve Yeni özel anahtar oluştur'a tıklayın. Bu, hizmet hesabı kimlik bilgilerini içeren bir .json
dosyasını indirecektir. Bu dosyayı bilinen bir konuma kaydedin.
Bu kadar! Next.js uygulamasını Firebase Authentication ile entegre etmeye hazırız
Bu kılavuzda Node.js ve npm'nin kurulu olduğu varsayılmaktadır. Bu eğitimde kullanılan komutlar, en son LTS Node.js v20 ile doğrulanmıştır. Terminalde node -v
çalıştırarak düğüm sürümünü doğrulayabilirsiniz. Node.js sürümleri arasında hızla geçiş yapmak için NVM gibi araçları da kullanabilirsiniz.
Favori terminalinizi açın, projeler klasörünüze gidin ve çalıştırın
npx create-next-app@latest
Basit tutmak için varsayılan yapılandırmayı kullanalım. Bu, TypeScript
ve tailwind
kullanacağımız anlamına gelir
✔ 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
Projenin kök dizinine gidelim ve tüm bağımlılıkların kurulu olduğundan emin olalım
cd my-app npm install
Her şeyin beklendiği gibi çalıştığını doğrulamak için Next.js dev sunucusunu npm run dev
komutuyla başlatalım. http://localhost:3000 dosyasını açtığınızda, aşağıdakine benzer Next.js karşılama sayfasını görmelisiniz:
Firebase ile entegrasyona başlamadan önce Firebase yapılandırmamızı depolamak ve okumak için güvenli bir yola ihtiyacımız var. Neyse ki Next.js yerleşik dotenv desteğiyle birlikte geliyor.
Favori kod düzenleyicinizi açın ve proje klasörüne gidin
Projenin kök dizininde .env.local
dosyasını oluşturalım ve onu aşağıdaki ortam değişkenleriyle dolduralım:
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=...
Lütfen NEXT_PUBLIC_
ön ekine sahip değişkenlerin istemci tarafı paketinde mevcut olacağını unutmayın. Firebase Auth Client SDK'yı kurmak için bunlara ihtiyacımız olacak
NEXT_PUBLIC_FIREBASE_PROJECT_ID
, FIREBASE_ADMIN_CLIENT_EMAIL
ve FIREBASE_ADMIN_PRIVATE_KEY
, hizmet hesabı kimlik bilgileri oluşturulduktan sonra indirilen .json
dosyasından alınabilir.
AUTH_COOKIE_NAME
kullanıcı kimlik bilgilerini saklamak için kullanılan çerezin adı olacaktır
AUTH_COOKIE_SIGNATURE_KEY_CURRENT
ve AUTH_COOKIE_SIGNATURE_KEY_PREVIOUS
, kimlik bilgilerini imzalayacağımız sırlardır
NEXT_PUBLIC_FIREBASE_API_KEY
, Proje Ayarları genel sayfasından alınan Web API Anahtarıdır
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN
proje kimliğinizdir .firebaseapp.com
NEXT_PUBLIC_FIREBASE_DATABASE_URL
proje kimliğinizdir .firebaseio.com
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID
, Proje Ayarları > Bulut Mesajlaşma sayfasından edinilebilir
USE_SECURE_COOKIES
yerel geliştirme için kullanılmayacak ancak uygulamamızı Vercel'e dağıttığımızda kullanışlı olacak
next-firebase-auth-edge
ve ilk yapılandırmanın kurulması npm install next-firebase-auth-edge@^1.4.1
çalıştırarak kitaplığı projenin bağımlılıklarına ekleyin.
Proje konfigürasyonumuzu kapsüllemek için config.ts
dosyasını oluşturalım. Zorunlu değildir ancak kod örneklerini daha okunaklı hale getirecektir.
Bu değerleri düşünmek için çok fazla zaman harcamayın. İlerledikçe bunları daha ayrıntılı olarak açıklayacağız.
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 };
Projenin kökünde middleware.ts
dosyasını oluşturun ve aşağıdakini yapıştırın
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", ], };
İster inanın ister inanmayın ama Uygulamamızın sunucusunu Firebase Authentication ile entegre ettik. Gerçekten kullanmaya başlamadan önce konfigürasyonu biraz açıklayalım:
loginPath
, authMiddleware'e GET /api/login
uç noktasını göstermesi talimatını verecektir. Bu uç nokta Authorization: Bearer ${idToken}
* üstbilgisiyle çağrıldığında, imzalı özel ve yenileme belirteçlerini içeren HTTP(S)-Only Set-Cookie
üstbilgisiyle yanıt verir.
* idToken
Firebase Client SDK'da bulunan getIdToken
işleviyle alınır. Bu konuda daha sonra daha fazla bilgi vereceğiz.
Benzer şekilde logoutPath
, ara yazılıma GET /api/logout
öğesini göstermesi talimatını verir, ancak herhangi bir ek başlık gerektirmez. Çağrıldığında, kimlik doğrulama çerezlerini tarayıcıdan kaldırır.
apiKey
Web API Anahtarıdır. Middleware bunu özel belirteci yenilemek ve kimlik bilgilerinin süresi dolduktan sonra kimlik doğrulama çerezlerini sıfırlamak için kullanır.
cookieName
/api/login
ve /api/logout
uç noktaları tarafından ayarlanan ve kaldırılan çerezin adıdır
cookieSignatureKeys
kullanıcı kimlik bilgilerinin imzalandığı gizli anahtarların listesi. Kimlik bilgileri her zaman listedeki ilk anahtarla imzalanacaktır, bu nedenle en az bir değer sağlamanız gerekir. Anahtar rotasyonu gerçekleştirmek için birden fazla anahtar sağlayabilirsiniz
cookieSerializeOptions
Set-Cookie
başlığını oluştururken çereze aktarılan seçeneklerdir. Daha fazla bilgi için README çerezine bakın
serviceAccount
kütüphaneye Firebase hizmetlerinizi kullanma yetkisi verir.
Eşleştirici, Next.js sunucusuna Middleware'i /api/login
, /api/logout
ve /
veya dosya veya API çağrısı olmayan diğer yollara karşı çalıştırması talimatını verir.
export const config = { matcher: [ "/", "/((?!_next|api|.*\\.).*)", "/api/login", "/api/logout", ], };
Neden tüm /api/*
çağrıları için ara yazılımı etkinleştirmediğimizi merak ediyor olabilirsiniz . Yapabiliriz, ancak kimlik doğrulaması yapılmamış çağrıları API rota işleyicisinin kendisinde işlemek iyi bir uygulamadır. Bu, bu eğitimin kapsamının biraz dışında, ancak ilgileniyorsanız bana bildirin, ben de bazı örnekler hazırlayayım!
Gördüğünüz gibi konfigürasyon minimum düzeydedir ve amacı açıkça tanımlanmıştır. Şimdi /api/login
ve /api/logout
uç noktalarımızı çağırmaya başlayalım.
İşleri olabildiğince basit hale getirmek için varsayılan Next.js ana sayfasını temizleyelim ve onu kişiselleştirilmiş içerikle değiştirelim
./app/page.tsx
açın ve şunu yapıştırın:
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> ); }
Bunu parça parça parçalayalım.
getTokens
işlevi, çerezlerden kullanıcı kimlik bilgilerini doğrulamak ve çıkarmak için tasarlanmıştır
const tokens = await getTokens(cookies(), { apiKey: clientConfig.apiKey, cookieName: serverConfig.cookieName, cookieSignatureKeys: serverConfig.cookieSignatureKeys, serviceAccount: serverConfig.serviceAccount, });
Kullanıcının kimliği doğrulanmadıysa veya iki özellik içeren bir nesne varsa null
ile çözümlenir:
API isteklerini harici arka uç hizmetlerine yetkilendirmek için kullanabileceğiniz idToken string
olan token
. Bu biraz kapsam dışı ama kütüphanenin dağıtılmış hizmet mimarisini mümkün kıldığını belirtmekte fayda var. token
tüm platformlardaki tüm resmi Firebase kitaplıklarıyla uyumludur ve kullanıma hazırdır.
decodedToken
adından da anlaşılacağı gibi, e-posta adresi, profil resmi ve özel talepler de dahil olmak üzere kullanıcıyı tanımlamak için gereken tüm bilgileri içeren ve ayrıca rollere ve izinlere göre erişimi kısıtlamamıza olanak tanıyan token
kodu çözülmüş sürümüdür.
tokens
aldıktan sonra, sayfanın yalnızca kimliği doğrulanmış kullanıcılar tarafından erişilebilir olduğundan emin olmak için next/navigation
notFound işlevini kullanırız
if (!tokens) { notFound(); }
Son olarak, bazı temel, kişiselleştirilmiş kullanıcı içeriklerini oluşturuyoruz
<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>
Hadi çalıştıralım.
Geliştirme sunucunuzu kapatmış olmanız durumunda, npm run dev
çalıştırmanız yeterlidir.
http://localhost:3000/ adresine erişmeye çalıştığınızda 404: Bu sayfa bulunamadı hatasını görmelisiniz.
Başarı! Sırlarımızı meraklı gözlerden koruduk!
firebase
yükleme ve Firebase Client SDK'yı başlatma Projenin kök dizininde npm install firebase
çalıştırın
İstemci SDK'sı yüklendikten sonra proje kök dizininde firebase.ts
dosyasını oluşturun ve aşağıdakini yapıştırın
import { initializeApp } from 'firebase/app'; import { clientConfig } from './config'; export const app = initializeApp(clientConfig);
Bu, Firebase İstemci SDK'sını başlatacak ve istemci bileşenleri için uygulama nesnesini açığa çıkaracaktır
Eğer kimse göremiyorsa, süper güvenli bir ana sayfaya sahip olmanın ne anlamı var? İnsanların uygulamamıza girmesine izin vermek için basit bir kayıt sayfası oluşturalım.
./app/register/page.tsx
altında yeni ve şık bir sayfa oluşturalım
"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> ); }
Biliyorum. Çok fazla metin var ama sabırlı olun.
"use client";
kayıt sayfasının istemci tarafı API'lerini kullanacağını belirtmek için
const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [confirmation, setConfirmation] = useState(""); const [error, setError] = useState("");
Daha sonra form durumumuzu korumak için bazı değişkenler ve ayarlayıcılar tanımlarız.
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); } }
Burada form gönderme mantığımızı tanımlıyoruz. Öncelikle password
ve confirmation
eşit olup olmadığını doğrularız, aksi halde hata durumunu güncelleriz. Değerler geçerli ise firebase/auth
adresinden createUserWithEmailAndPassword
ile kullanıcı hesabı oluşturuyoruz. Bu adım başarısız olursa (örneğin e-postanın alınması), hatayı güncelleyerek kullanıcıyı bilgilendiririz.
Her şey yolunda giderse kullanıcıyı /login
sayfasına yönlendiririz. Muhtemelen şu anda kafanız karışıktır ve bunda haklısınız. /login
sayfası henüz mevcut değil. Sadece bundan sonra olacaklara hazırlanıyoruz.
http://localhost:3000/register adresini ziyaret ettiğinizde sayfa kabaca şöyle görünmelidir:
Artık kullanıcılar kaydolabildiğine göre kimliklerini kanıtlamalarına izin verin
./app/login/page.tsx
altında bir giriş sayfası oluşturun
"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'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> ); }
Gördüğünüz gibi kayıt sayfasına oldukça benziyor. Önemli kısma odaklanalım:
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); } }
Bütün sihrin gerçekleştiği yer orası. Kullanıcının idToken
almak için firebase/auth
signInEmailAndPassword
kullanıyoruz.
Daha sonra ara yazılımın açığa çıkardığı /api/login
uç noktasını çağırıyoruz. Bu uç nokta, tarayıcı çerezlerimizi kullanıcı kimlik bilgileriyle günceller.
Son olarak router.push("/");
çağırarak kullanıcıyı ana sayfaya yönlendiriyoruz.
Giriş sayfası kabaca bu şekilde görünmelidir
Haydi test edelim!
http://localhost:3000/register adresine gidin, bir hesap oluşturmak için rastgele bir e-posta adresi ve şifre girin. Bu kimlik bilgilerini http://localhost:3000/login sayfasında kullanın. Enter'a tıkladıktan sonra süper güvenli ana sayfaya yönlendirilmelisiniz.
Sonunda kendi kişisel , ultra güvenli ana sayfamızı görebildik! Fakat bekle! Nasıl çıkacağız?
Kendimizi sonsuza kadar (veya 12 gün) dünyadan mahrum kalmamak için çıkış butonu eklememiz gerekiyor.
Başlamadan önce Firebase Client SDK'yı kullanarak oturumumuzu kapatabilecek bir istemci bileşeni oluşturmamız gerekiyor.
./app/HomePage.tsx
altında yeni bir dosya oluşturalım
"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> ); }
Fark etmiş olabileceğiniz gibi bu, ./app/page.tsx
dosyamızın biraz değiştirilmiş versiyonudur. getTokens
yalnızca sunucu bileşenleri ve API rota işleyicileri içinde çalıştığından, signOut
ve useRouter
istemci bağlamında çalıştırılması gerektiğinden, ayrı bir istemci bileşeni oluşturmamız gerekiyordu. Biraz karmaşık biliyorum ama aslında oldukça güçlü. Daha sonra açıklayacağım.
Oturum kapatma işlemine odaklanalım
const router = useRouter(); async function handleLogout() { await signOut(getAuth(app)); await fetch("/api/logout"); router.push("/login"); }
Öncelikle Firebase Client SDK'dan çıkış yapıyoruz. Daha sonra ara yazılımın açığa çıkardığı /api/logout
uç noktasını çağırıyoruz. Kullanıcıyı /login
sayfasına yönlendirerek bitiriyoruz.
Sunucu ana sayfamızı güncelleyelim. ./app/page.tsx
adresine gidin ve aşağıdakini yapıştırın
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} />; }
Artık Home
sunucu bileşenimiz yalnızca kullanıcı belirteçlerini almak ve bunu HomePage
istemci bileşenine aktarmaktan sorumludur. Bu aslında oldukça yaygın ve kullanışlı bir kalıptır.
Bunu test edelim:
İşte! Artık kendi isteğimizle uygulamaya giriş ve çıkış yapabiliyoruz. Mükemmel!
Yoksa öyle mi?
Kimliği doğrulanmamış kullanıcı http://localhost:3000/ adresini açarak ana sayfaya girmeye çalıştığında 404: Bu sayfa bulunamadı.
Ayrıca, kimliği doğrulanmış kullanıcılar , http://localhost:3000/register ve http://localhost:3000/login sayfalarına, çıkış yapmak zorunda kalmadan erişmeye devam edebilir.
Daha iyisini yapabiliriz.
Görünüşe göre bazı yönlendirme mantığı eklememiz gerekiyor. Bazı kuralları tanımlayalım:
/register
ve /login
sayfalarına erişmeye çalıştığında, onları /
adresine yönlendirmeliyiz/
sayfaya erişmeye çalıştığında, onları /login
yönlendirmeliyiz
Middleware, Next.js uygulamalarındaki yönlendirmeleri işlemenin en iyi yollarından biridir. Neyse ki authMiddleware
, çok çeşitli yeniden yönlendirme senaryolarını yönetmek için çok sayıda seçeneği ve yardımcı işlevi destekler.
middleware.ts
dosyasını açıp bu güncel sürümü yapıştıralım.
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", ], };
Bu olmalı. Tüm yönlendirme kurallarını uyguladık. Hadi bunu parçalayalım.
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
isteğe geçerli kullanıcı kimlik bilgileri eklendiğinde çağrılır; kullanıcının kimliği doğrulanır. İlk olarak tokens
nesnesi ve ikinci argüman olarak Değiştirilmiş İstek Başlıkları ile çağrılır. NextResponse
ile çözülmesi gerekir.
next-firebase-auth-edge
redirectToHome
NextResponse.redirect(new URL(“/“))
olarak basitleştirilebilecek bir nesneyi döndüren bir yardımcı işlevdir.
PUBLIC_PATHS.includes(request.nextUrl.pathname)
kontrol ederek, kimliği doğrulanmış kullanıcının /login
veya /register
sayfasına erişmeye çalışıp çalışmadığını doğrularız ve bu durumda ana sayfaya yönlendirme yaparız.
handleInvalidToken: async (reason) => { console.info('Missing or malformed credentials', {reason}); return redirectToLogin(request, { path: '/login', publicPaths: PUBLIC_PATHS }); },
handleInvalidToken
beklenen bir şey olduğunda çağrılır. Bu beklenen olaylardan biri, kullanıcının uygulamanızı ilk kez başka bir cihazdan görmesi veya kimlik bilgilerinin süresi dolduktan sonra görmesidir.
Kimliği doğrulanmamış kullanıcı için handleInvalidToken
çağrıldığını bildiğimizden ikinci kurala geçebiliriz: Kimliği doğrulanmamış kullanıcı /
sayfaya erişmeye çalıştığında , onları /login
yönlendirmeliyiz .
Karşılanması gereken başka bir koşul olmadığından, sadece NextResponse.redirect(new URL(“/login”))
olarak basitleştirilebilecek redirectToLogin
sonucunu döndürürüz. Ayrıca kullanıcının yönlendirme döngüsüne düşmemesini de sağlar.
Son olarak,
handleError: async (error) => { console.error('Unhandled authentication error', {error}); return redirectToLogin(request, { path: '/login', publicPaths: PUBLIC_PATHS }); }
handleInvalidToken
aksine, beklenmedik* bir şey olduğunda ve muhtemelen araştırılması gerektiğinde handleError
çağrılır. Dokümantasyonda açıklamalarıyla birlikte olası hataların bir listesini bulabilirsiniz.
Bir hata durumunda bu durumu günlüğe kaydediyor ve kullanıcıyı güvenli bir şekilde giriş sayfasına yönlendiriyoruz.
* handleError
Google ortak anahtarları güncellendikten sonra INVALID_ARGUMENT
hatasıyla çağrılabilir.
Bu bir anahtar döndürme şeklidir ve beklenen bir durumdur. Daha fazla bilgi için bu Github sayısına bakın
İşte bu kadar. Nihayet.
Web uygulamamızdan çıkış yapalım ve http://localhost:3000/ adresini açalım. /login
sayfasına yönlendirilmeliyiz.
Tekrar giriş yapalım ve http://localhost:3000/login girmeyi deneyelim. /
sayfasına yönlendirilmeliyiz.
Sorunsuz kullanıcı deneyimi sağlamakla kalmadık. next-firebase-auth-edge
yalnızca Uygulamanın sunucusunda çalışan ve ek istemci tarafı kodu sağlamayan sıfır paket boyutlu kitaplıktır. Ortaya çıkan paket gerçekten minimum düzeydedir. Mükemmel diye buna derim.
Uygulamamız artık hem Sunucu hem de İstemci bileşenlerinde Firebase Authentication ile tamamen entegredir. Next.js'nin tüm potansiyelini ortaya çıkarmaya hazırız!
Uygulamanın kaynak kodu next-firebase-auth-edge/examples/next-typescript-minimal konumunda bulunabilir
Bu kılavuzda yeni Next.js uygulamasını Firebase Authentication ile entegre etmeyi inceledik.
Oldukça kapsamlı olmasına rağmen makale, kimlik doğrulama akışının parola sıfırlama formu veya e-posta ve parola dışındaki oturum açma yöntemleri gibi bazı önemli kısımlarını atladı.
Kitaplıkla ilgileniyorsanız, tam teşekküllü next-firebase-auth-edge başlangıç demo sayfasını önizleyebilirsiniz.
Firestore entegrasyonu , Sunucu Eylemleri , Uygulama Kontrolü desteği ve daha fazlasını içerir
Kütüphane tonlarca örnek içeren özel bir dokümantasyon sayfası sağlar
Makaleyi beğendiyseniz, next-firebase-auth-edge deposuna yıldız eklemekten memnuniyet duyarım. Şerefe! 🎉
Bu bonus kılavuz size Next.js uygulamanızı Vercel'e nasıl dağıtacağınızı öğretecek
Vercel'e dağıtım yapabilmek için yeni uygulamanız için bir depo oluşturmanız gerekecektir.
https://github.com/ adresine gidin ve yeni bir depo oluşturun.
create-next-app
bizim için zaten yerel bir git deposu başlattı, bu nedenle projenizin kök klasörünü takip etmeniz ve çalıştırmanız yeterli:
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
https://vercel.com/ adresine gidin ve Github hesabınızla oturum açın
Giriş yaptıktan sonra Vercel'in Genel Bakış sayfasına gidin ve Yeni Ekle > Proje'ye tıklayın.
Yeni oluşturduğumuz Github deposunun yanındaki İçe Aktar'a tıklayın. Henüz konuşlandırmayın.
Dağıtımdan önce proje yapılandırmasını sağlamamız gerekiyor. Bazı ortam değişkenlerini ekleyelim:
Vercel varsayılan olarak HTTPS kullandığından USE_SECURE_COOKIES
true
olarak ayarlamayı unutmayın.
Artık Dağıt'a tıklamaya hazırız
Bir veya iki dakika bekleyin; uygulamanıza şuna benzer bir URL ile erişebilmeniz gerekir: https://next-typescript-minimal-xi.vercel.app/
Tamamlamak. Eminim bu kadar kolay olmasını beklemiyordun.
Rehberi beğendiyseniz, bir sonraki firebase-auth-edge deposunun başrolde yer almasını memnuniyetle karşılarım.
Ayrıca görüşlerinizi yorumlarda bana bildirebilirsiniz. Şerefe! 🎉