Firebase Auth, hizmet çalışanlarını algılama ve iletme özelliği sunar. Oturum yönetimi için Firebase kimlik jetonları. Bu durum, şu avantajları sağlar:
- Sunucudan gelen her HTTP isteğinde herhangi bir kod olmadan kimlik jetonu iletebilme gerek yok.
- Kimlik jetonunu ek gidiş dönüş ya da gecikmeleri telafi eder.
- Arka uç ve ön uç senkronize oturumları. Erişmesi gereken uygulamalar Realtime Database, Firestore vb. Firebase hizmetleri ve bazı harici sunucu tarafı kaynağı (ör. SQL veritabanı) bu çözümü kullanabilir. Ayrıca, aynı oturuma Service Worker üzerinden de erişilebilir. veya paylaşılan çalışana izin verilir.
- Her sayfaya Firebase Auth kaynak kodu ekleme ihtiyacını ortadan kaldırır (gecikmeyi azaltır). Bir kez yüklenip başlatılan hizmet çalışanı Tüm istemciler için oturum yönetimini arka planda yürütür.
Genel Bakış
Firebase Auth, istemci tarafında çalışacak şekilde optimize edilmiştir. Jetonlar kaydedilir Web depolama alanı. Böylece, diğer Firebase hizmetleriyle de entegrasyon kolaylaşır. (ör. Realtime Database, Cloud Firestore, Cloud Storage vb.) Oturumları sunucu tarafı bakış açısından yönetmek için kimlik jetonlarının alınıp sunucuya iletilmelidir.
Web
import { getAuth, getIdToken } from "firebase/auth"; const auth = getAuth(); getIdToken(auth.currentUser) .then((idToken) => { // idToken can be passed back to server. }) .catch((error) => { // Error occurred. });
Web
firebase.auth().currentUser.getIdToken() .then((idToken) => { // idToken can be passed back to server. }) .catch((error) => { // Error occurred. });
Ancak bu, dosyayı almak için bazı komut dosyalarının istemciden çalışması gerektiği ve ardından istek başlığı (POST) aracılığıyla sunucuya iletebilir. gövde vb.
Bu durumda ölçeklenmeyebilir ve bunun yerine sunucu tarafı oturum çerezleri gerekebilir. Kimlik jetonları oturum çerezleri olarak ayarlanabilir ancak bunlar kısa ömürlüdür ve için istemciden yenilenmesi ve ardından geçerlilik süresi sona erdiğinde yeni çerezler olarak ayarlanması gerekir Bunlar, kullanıcı mağazayı ziyaret etmemişse ek gidiş dönüş inceleyeceğiz.
Firebase Auth,
daha geleneksel bir yaklaşım sunar
çerez tabanlı oturum yönetimi çözümünü kullanabilir
bu çözüm, sunucu tarafı httpOnly
çerezi tabanlı uygulamalarda en iyi sonucu verir.
Bu da istemci jetonları ve sunucu tarafı jetonları alınabileceği için yönetilmesi
özellikle de istemci tabanlı diğer Firebase'i de kullanmanız gerekiyorsa
kullanıma sunuyoruz.
Bunun yerine, sunucu tarafındaki kullanıcı oturumlarını yönetmek için Service Worker'lar kullanılabilir tüketim. Bunun nedeni aşağıdakilerden biri olabilir:
- Hizmet çalışanlarının mevcut Firebase Auth durumuna erişimi vardır. Şu anki kullanıcı kimliği jetonu, hizmet çalışanından alınabilir. Jeton süresi dolduysa istemci SDK onu yeniler ve yeni bir tane döndürür.
- Service Worker'lar, getirme isteklerine müdahale edip bunları değiştirebilir.
Hizmet çalışanı değişiklikleri
Hizmet çalışanının, Auth kitaplığını ve geçerli kimlik jetonunu geri yükleyebilirsiniz.
Web
import { initializeApp } from "firebase/app"; import { getAuth, onAuthStateChanged, getIdToken } from "firebase/auth"; // Initialize the Firebase app in the service worker script. initializeApp(config); /** * Returns a promise that resolves with an ID token if available. * @return {!Promise<?string>} The promise that resolves with an ID token if * available. Otherwise, the promise resolves with null. */ const auth = getAuth(); const getIdTokenPromise = () => { return new Promise((resolve, reject) => { const unsubscribe = onAuthStateChanged(auth, (user) => { unsubscribe(); if (user) { getIdToken(user).then((idToken) => { resolve(idToken); }, (error) => { resolve(null); }); } else { resolve(null); } }); }); };
Web
// Initialize the Firebase app in the service worker script. firebase.initializeApp(config); /** * Returns a promise that resolves with an ID token if available. * @return {!Promise<?string>} The promise that resolves with an ID token if * available. Otherwise, the promise resolves with null. */ const getIdToken = () => { return new Promise((resolve, reject) => { const unsubscribe = firebase.auth().onAuthStateChanged((user) => { unsubscribe(); if (user) { user.getIdToken().then((idToken) => { resolve(idToken); }, (error) => { resolve(null); }); } else { resolve(null); } }); }); };
Uygulamanın kaynağına gönderilen tüm getirme isteklerine müdahale edilir ve bir kimlik jetonu kullanılabilir hale getirildiğinden emin olun. Sunucu tarafı, istek başlıkları, kimlik jetonu için kontrol edilerek doğrulanır ve işlenir. Service Worker komut dosyasında, getirme isteğine müdahale edilir ve değiştirildi.
Web
const getOriginFromUrl = (url) => { // https://stackoverflow.com/questions/1420881/how-to-extract-base-url-from-a-string-in-javascript const pathArray = url.split('/'); const protocol = pathArray[0]; const host = pathArray[2]; return protocol + '//' + host; }; // Get underlying body if available. Works for text and json bodies. const getBodyContent = (req) => { return Promise.resolve().then(() => { if (req.method !== 'GET') { if (req.headers.get('Content-Type').indexOf('json') !== -1) { return req.json() .then((json) => { return JSON.stringify(json); }); } else { return req.text(); } } }).catch((error) => { // Ignore error. }); }; self.addEventListener('fetch', (event) => { /** @type {FetchEvent} */ const evt = event; const requestProcessor = (idToken) => { let req = evt.request; let processRequestPromise = Promise.resolve(); // For same origin https requests, append idToken to header. if (self.location.origin == getOriginFromUrl(evt.request.url) && (self.location.protocol == 'https:' || self.location.hostname == 'localhost') && idToken) { // Clone headers as request headers are immutable. const headers = new Headers(); req.headers.forEach((val, key) => { headers.append(key, val); }); // Add ID token to header. headers.append('Authorization', 'Bearer ' + idToken); processRequestPromise = getBodyContent(req).then((body) => { try { req = new Request(req.url, { method: req.method, headers: headers, mode: 'same-origin', credentials: req.credentials, cache: req.cache, redirect: req.redirect, referrer: req.referrer, body, // bodyUsed: req.bodyUsed, // context: req.context }); } catch (e) { // This will fail for CORS requests. We just continue with the // fetch caching logic below and do not pass the ID token. } }); } return processRequestPromise.then(() => { return fetch(req); }); }; // Fetch the resource after checking for the ID token. // This can also be integrated with existing logic to serve cached files // in offline mode. evt.respondWith(getIdTokenPromise().then(requestProcessor, requestProcessor)); });
Web
const getOriginFromUrl = (url) => { // https://stackoverflow.com/questions/1420881/how-to-extract-base-url-from-a-string-in-javascript const pathArray = url.split('/'); const protocol = pathArray[0]; const host = pathArray[2]; return protocol + '//' + host; }; // Get underlying body if available. Works for text and json bodies. const getBodyContent = (req) => { return Promise.resolve().then(() => { if (req.method !== 'GET') { if (req.headers.get('Content-Type').indexOf('json') !== -1) { return req.json() .then((json) => { return JSON.stringify(json); }); } else { return req.text(); } } }).catch((error) => { // Ignore error. }); }; self.addEventListener('fetch', (event) => { /** @type {FetchEvent} */ const evt = event; const requestProcessor = (idToken) => { let req = evt.request; let processRequestPromise = Promise.resolve(); // For same origin https requests, append idToken to header. if (self.location.origin == getOriginFromUrl(evt.request.url) && (self.location.protocol == 'https:' || self.location.hostname == 'localhost') && idToken) { // Clone headers as request headers are immutable. const headers = new Headers(); req.headers.forEach((val, key) => { headers.append(key, val); }); // Add ID token to header. headers.append('Authorization', 'Bearer ' + idToken); processRequestPromise = getBodyContent(req).then((body) => { try { req = new Request(req.url, { method: req.method, headers: headers, mode: 'same-origin', credentials: req.credentials, cache: req.cache, redirect: req.redirect, referrer: req.referrer, body, // bodyUsed: req.bodyUsed, // context: req.context }); } catch (e) { // This will fail for CORS requests. We just continue with the // fetch caching logic below and do not pass the ID token. } }); } return processRequestPromise.then(() => { return fetch(req); }); }; // Fetch the resource after checking for the ID token. // This can also be integrated with existing logic to serve cached files // in offline mode. evt.respondWith(getIdToken().then(requestProcessor, requestProcessor)); });
Sonuç olarak, kimliği doğrulanmış tüm istekler için her zaman başlığın başka bir işlem yapılmadan doldurulmasını sağlar.
Hizmet çalışanının, Yetkilendirme durumu değişikliklerini algılaması için yüklü olduğundan emin olun. Lütfen hizmet çalışanı, tarayıcı düzgün kapatıldıktan sonra da çalışmaya devam edecek kapatıldı.
Kurulumdan sonra hizmet,
şu şekilde ayarlanabilmesi için, çalışanın etkinleştirme sırasında clients.claim()
yöntemini çağırması gerekir:
için denetleyici ekleyin.
Web
self.addEventListener('activate', (event) => { event.waitUntil(clients.claim()); });
Web
self.addEventListener('activate', (event) => { event.waitUntil(clients.claim()); });
İstemci tarafı değişiklikler
Destekleniyorsa Service Worker'ın istemci tarafında yüklenmesi gerekir. kayıt sayfasını ziyaret edin.
Web
// Install servicerWorker if supported on sign-in/sign-up page. if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/service-worker.js', {scope: '/'}); }
Web
// Install servicerWorker if supported on sign-in/sign-up page. if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/service-worker.js', {scope: '/'}); }
Kullanıcı oturum açıp başka bir sayfaya yönlendirildiğinde, hizmet çalışanı yönlendirme tamamlanmadan önce başlığa kimlik jetonunu yerleştirebilir.
Web
import { getAuth, signInWithEmailAndPassword } from "firebase/auth"; // Sign in screen. const auth = getAuth(); signInWithEmailAndPassword(auth, email, password) .then((result) => { // Redirect to profile page after sign-in. The service worker will detect // this and append the ID token to the header. window.location.assign('/profile'); }) .catch((error) => { // Error occurred. });
Web
// Sign in screen. firebase.auth().signInWithEmailAndPassword(email, password) .then((result) => { // Redirect to profile page after sign-in. The service worker will detect // this and append the ID token to the header. window.location.assign('/profile'); }) .catch((error) => { // Error occurred. });
Sunucu tarafı değişiklikler
Sunucu tarafı kodu, her istekte kimlik jetonunu algılayabilir. Bu
Bu davranış, Node.js için Yönetici SDK'sı veya
FirebaseServerApp
kullanan SDK.
Node.js
// Server side code.
const admin = require('firebase-admin');
// The Firebase Admin SDK is used here to verify the ID token.
admin.initializeApp();
function getIdToken(req) {
// Parse the injected ID token from the request header.
const authorizationHeader = req.headers.authorization || '';
const components = authorizationHeader.split(' ');
return components.length > 1 ? components[1] : '';
}
function checkIfSignedIn(url) {
return (req, res, next) => {
if (req.url == url) {
const idToken = getIdToken(req);
// Verify the ID token using the Firebase Admin SDK.
// User already logged in. Redirect to profile page.
admin.auth().verifyIdToken(idToken).then((decodedClaims) => {
// User is authenticated, user claims can be retrieved from
// decodedClaims.
// In this sample code, authenticated users are always redirected to
// the profile page.
res.redirect('/profile');
}).catch((error) => {
next();
});
} else {
next();
}
};
}
// If a user is signed in, redirect to profile page.
app.use(checkIfSignedIn('/'));
Web modüler API
import { initializeServerApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
import { headers } from 'next/headers';
import { redirect } from 'next/navigation';
export default function MyServerComponent() {
// Get relevant request headers (in Next.JS)
const authIdToken = headers().get('Authorization')?.split('Bearer ')[1];
// Initialize the FirebaseServerApp instance.
const serverApp = initializeServerApp(firebaseConfig, { authIdToken });
// Initialize Firebase Authentication using the FirebaseServerApp instance.
const auth = getAuth(serverApp);
if (auth.currentUser) {
redirect('/profile');
}
// ...
}
Sonuç
Ayrıca, kimlik jetonları hizmet çalışanları ve hizmet aracılığıyla ayarlanacağı için çalışanların aynı kaynaktan çalışacak şekilde kısıtlandığı için CSRF riski yoktur çünkü uç noktalarınızı çağırmaya çalışan farklı kaynaklı bir web sitesi hizmet çalışanı çağrılamaz ve istek görünür hale gelir. açısından bir kimlik doğrulaması yapılmamıştır.
Service Worker'lar artık başlıca tüm modern tarayıcılarda desteklense de eski tarayıcılar tarafından desteklenmez. Bu nedenle, bazı yedekler Service Worker'lar olmadığında kimlik jetonunu sunucunuza iletmek için veya uygulamanın yalnızca şu sürümleri destekleyen tarayıcılarda çalışması kısıtlanabilir: hizmet çalışanları.
Hizmet çalışanlarının yalnızca tek kaynaklı olduğunu ve sadece yükleneceğini unutmayın https bağlantısı veya localhost üzerinden sunulan web sitelerinde
Service Worker için tarayıcı desteği hakkında daha fazla bilgi: caniuse.com'a gidin.
Faydalı bağlantılar
- Oturum yönetimi için hizmet çalışanlarını kullanma hakkında daha fazla bilgi edinmek üzere dışarıda GitHub'da örnek uygulama kaynak kodu'nu ziyaret edin.
- Yukarıdakinin dağıtılmış bir örnek uygulaması şurada mevcut: https://auth-service-worker.appspot.com