paint-brush
Firebase Kimlik Doğrulamasını En Son Next.js Özellikleriyle Kullanmaile@awinogrodzki
26,729 okumalar
26,729 okumalar

Firebase Kimlik Doğrulamasını En Son Next.js Özellikleriyle Kullanma

ile Amadeusz Winogrodzki32m2024/04/04
Read on Terminal Reader

Çok uzun; Okumak

Sıfır paket boyutundaki "next-firebase-auth-edge" kitaplığını kullanarak Firebase Kimlik Doğrulamasını Next.js ile entegre etmeye ilişkin kapsamlı, adım adım kılavuz. Sorunsuz bir kullanıcı deneyimi için yönlendirme mantığının yanı sıra kullanıcı kaydı, oturum açma ve oturum kapatma işlevlerine yönelik adımları içerir. Bu kılavuzda Firebase Kimlik Doğrulamasını Uygulama Yönlendiricisi, Ara Yazılım ve Sunucu Bileşenleri gibi en yeni Next.js özellikleriyle nasıl entegre edeceğinizi öğreneceksiniz. Uygulamanın Vercel'e dağıtılmasına ilişkin talimatlarla sona eriyor ve kitaplığın kullanım kolaylığını ve Next.js uygulamalarını Firebase Authentication ile geliştirmek isteyen geliştiriciler için geleceğe yönelik tasarımını sergiliyor.
featured image - Firebase Kimlik Doğrulamasını En Son Next.js Özellikleriyle Kullanma
Amadeusz Winogrodzki HackerNoon profile picture
0-item
1-item

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.


Nasıl başladı

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.


Nasıl gidiyor

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.



Diğer benliğim



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.


Firebase'i kurma

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


E-posta/Parolayla oturum açma yöntemini etkinleştirme


İ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.


Web API Anahtarını Alın


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.


Gönderen Kimliğini Al


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

Next.js uygulamasını sıfırdan oluşturma

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.

Next.js uygulamasını CLI ile kurma

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:


Next.js karşılama sayfası

Ortam değişkenlerini hazırlama

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

Firebase Authentication ile entegrasyon

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 };


Ara Yazılım Ekleme

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.


Güvenli bir ana sayfa oluşturma

İş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

Kayıt sayfası oluşturma

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:


Kayıt sayfası


Giriş sayfası oluşturma

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&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> ); }


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.


Süper güvenli ana sayfa




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:

  • Kimliği doğrulanmış kullanıcı /register ve /login sayfalarına erişmeye çalıştığında, onları / adresine yönlendirmeliyiz
  • Kimliği doğrulanmamış kullanıcı / 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


Sonsöz

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! 🎉



Bonus – Uygulamayı Vercel'e dağıtma

Bu bonus kılavuz size Next.js uygulamanızı Vercel'e nasıl dağıtacağınızı öğretecek

Git deposu oluşturma

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


Yeni Vercel projesi ekleniyor

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'e dağıtım


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! 🎉