सेवा कर्मियों के साथ सत्र प्रबंधन

फायरबेस ऑथ सत्र प्रबंधन के लिए फायरबेस आईडी टोकन का पता लगाने और पास करने के लिए सेवा कर्मियों का उपयोग करने की क्षमता प्रदान करता है। इससे निम्नलिखित लाभ मिलते हैं:

  • बिना किसी अतिरिक्त कार्य के सर्वर से प्रत्येक HTTP अनुरोध पर एक आईडी टोकन पास करने की क्षमता।
  • बिना किसी अतिरिक्त राउंड ट्रिप या विलंब के आईडी टोकन को ताज़ा करने की क्षमता।
  • बैकएंड और फ्रंटएंड सिंक्रनाइज़ सत्र। ऐसे एप्लिकेशन जिन्हें फ़ायरबेस सेवाओं जैसे रीयलटाइम डेटाबेस, फ़ायरस्टोर, आदि और कुछ बाहरी सर्वर साइड संसाधन (एसक्यूएल डेटाबेस, आदि) तक पहुंचने की आवश्यकता है, वे इस समाधान का उपयोग कर सकते हैं। इसके अलावा, उसी सत्र को सेवा कार्यकर्ता, वेब कार्यकर्ता या साझा कार्यकर्ता से भी एक्सेस किया जा सकता है।
  • प्रत्येक पृष्ठ पर फायरबेस प्रामाणिक स्रोत कोड शामिल करने की आवश्यकता समाप्त हो जाती है (विलंबता कम हो जाती है)। सेवा कार्यकर्ता, एक बार लोड और आरंभीकृत, पृष्ठभूमि में सभी ग्राहकों के लिए सत्र प्रबंधन संभालेगा।

अवलोकन

फायरबेस ऑथ को क्लाइंट साइड पर चलाने के लिए अनुकूलित किया गया है। टोकन वेब स्टोरेज में सहेजे जाते हैं। इससे अन्य फायरबेस सेवाओं जैसे रीयलटाइम डेटाबेस, क्लाउड फायरस्टोर, क्लाउड स्टोरेज इत्यादि के साथ एकीकृत करना भी आसान हो जाता है। सर्वर साइड परिप्रेक्ष्य से सत्र प्रबंधित करने के लिए, आईडी टोकन को पुनर्प्राप्त करना होगा और सर्वर को पास करना होगा।

Web modular API

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 namespaced API

firebase.auth().currentUser.getIdToken()
  .then((idToken) => {
    // idToken can be passed back to server.
  })
  .catch((error) => {
    // Error occurred.
  });

हालाँकि, इसका मतलब यह है कि नवीनतम आईडी टोकन प्राप्त करने के लिए क्लाइंट से कुछ स्क्रिप्ट चलानी होगी और फिर इसे अनुरोध हेडर, POST बॉडी आदि के माध्यम से सर्वर तक भेजना होगा।

यह स्केल नहीं हो सकता है और इसके बजाय सर्वर साइड सत्र कुकीज़ की आवश्यकता हो सकती है। आईडी टोकन को सत्र कुकीज़ के रूप में सेट किया जा सकता है लेकिन ये अल्पकालिक होते हैं और इन्हें क्लाइंट से ताज़ा करने की आवश्यकता होगी और फिर समाप्ति पर नई कुकीज़ के रूप में सेट करना होगा जिसके लिए अतिरिक्त राउंड ट्रिप की आवश्यकता हो सकती है यदि उपयोगकर्ता ने कुछ समय में साइट का दौरा नहीं किया है।

जबकि फायरबेस ऑथ एक अधिक पारंपरिक कुकी आधारित सत्र प्रबंधन समाधान प्रदान करता है, यह समाधान सर्वर साइड httpOnly कुकी आधारित अनुप्रयोगों के लिए सबसे अच्छा काम करता है और इसे प्रबंधित करना कठिन है क्योंकि क्लाइंट टोकन और सर्वर साइड टोकन सिंक से बाहर हो सकते हैं, खासकर यदि आपको भी उपयोग करने की आवश्यकता है अन्य ग्राहक आधारित फायरबेस सेवाएँ।

इसके बजाय, सर्वर साइड खपत के लिए उपयोगकर्ता सत्रों को प्रबंधित करने के लिए सेवा कर्मियों का उपयोग किया जा सकता है। यह निम्नलिखित के कारण काम करता है:

  • सेवा कर्मियों के पास वर्तमान फायरबेस प्रामाणिक स्थिति तक पहुंच है। वर्तमान उपयोगकर्ता आईडी टोकन सेवा कार्यकर्ता से प्राप्त किया जा सकता है। यदि टोकन समाप्त हो गया है, तो क्लाइंट एसडीके इसे रीफ्रेश करेगा और एक नया लौटाएगा।
  • सेवा कर्मी फ़ेच अनुरोधों को रोक सकते हैं और उन्हें संशोधित कर सकते हैं।

सेवा कर्मी बदलता है

यदि उपयोगकर्ता साइन इन है तो सेवा कार्यकर्ता को प्रामाणिक लाइब्रेरी और वर्तमान आईडी टोकन प्राप्त करने की क्षमता शामिल करने की आवश्यकता होगी।

Web modular API

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 namespaced API

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

ऐप के मूल से संबंधित सभी फ़ेच अनुरोधों को रोक दिया जाएगा और यदि कोई आईडी टोकन उपलब्ध है, तो उसे हेडर के माध्यम से अनुरोध में जोड़ दिया जाएगा। सर्वर साइड, अनुरोध हेडर को आईडी टोकन के लिए जांचा जाएगा, सत्यापित और संसाधित किया जाएगा। सर्विस वर्कर स्क्रिप्ट में, फ़ेच अनुरोध को इंटरसेप्ट किया जाएगा और संशोधित किया जाएगा।

Web modular API

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 namespaced API

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

परिणामस्वरूप, सभी प्रमाणित अनुरोधों में अतिरिक्त प्रसंस्करण के बिना हेडर में हमेशा एक आईडी टोकन पारित किया जाएगा।

सेवा कार्यकर्ता को प्रामाणिक स्थिति परिवर्तनों का पता लगाने के लिए, इसे आमतौर पर साइन-इन/साइन-अप पृष्ठ पर स्थापित करना होगा। इंस्टालेशन के बाद, सेवा कर्मी को सक्रियण पर clients.claim() कॉल करना होगा ताकि इसे वर्तमान पृष्ठ के लिए नियंत्रक के रूप में सेटअप किया जा सके।

Web modular API

self.addEventListener('activate', (event) => {
  event.waitUntil(clients.claim());
});

Web namespaced API

self.addEventListener('activate', (event) => {
  event.waitUntil(clients.claim());
});

ग्राहक पक्ष परिवर्तन

यदि सेवा कर्मी समर्थित है, तो उसे क्लाइंट साइड साइन-इन/साइन-अप पृष्ठ पर स्थापित करने की आवश्यकता है।

Web modular API

// Install servicerWorker if supported on sign-in/sign-up page.
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/service-worker.js', {scope: '/'});
}

Web namespaced API

// Install servicerWorker if supported on sign-in/sign-up page.
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/service-worker.js', {scope: '/'});
}

जब उपयोगकर्ता साइन इन होता है और दूसरे पेज पर रीडायरेक्ट होता है, तो सेवा कर्मी रीडायरेक्ट पूरा होने से पहले हेडर में आईडी टोकन इंजेक्ट करने में सक्षम होगा।

Web modular API

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 namespaced API

// 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.
  });

सर्वर साइड परिवर्तन

सर्वर साइड कोड हर अनुरोध पर आईडी टोकन का पता लगाने में सक्षम होगा। इसे निम्नलिखित Node.js एक्सप्रेस नमूना कोड में दर्शाया गया है।

// Server side code.
const admin = require('firebase-admin');
const serviceAccount = require('path/to/serviceAccountKey.json');

// The Firebase Admin SDK is used here to verify the ID token.
admin.initializeApp({
  credential: admin.credential.cert(serviceAccount)
});

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('/'));

निष्कर्ष

इसके अलावा, चूंकि आईडी टोकन सेवा कर्मियों के माध्यम से सेट किए जाएंगे, और सेवा कर्मियों को एक ही मूल से चलाने के लिए प्रतिबंधित किया गया है, इसलिए सीएसआरएफ का कोई जोखिम नहीं है क्योंकि आपके एंडपॉइंट पर कॉल करने का प्रयास करने वाली विभिन्न मूल की वेबसाइट सेवा कार्यकर्ता को आमंत्रित करने में विफल हो जाएगी। , जिससे अनुरोध सर्वर के दृष्टिकोण से अप्रमाणित प्रतीत होता है।

जबकि सेवा कर्मी अब सभी आधुनिक प्रमुख ब्राउज़रों में समर्थित हैं, कुछ पुराने ब्राउज़र उनका समर्थन नहीं करते हैं। परिणामस्वरूप, जब सेवा कर्मी उपलब्ध नहीं होते हैं तो आपके सर्वर पर आईडी टोकन पास करने के लिए कुछ फ़ॉलबैक की आवश्यकता हो सकती है या किसी ऐप को केवल सेवा कर्मियों का समर्थन करने वाले ब्राउज़र पर चलाने के लिए प्रतिबंधित किया जा सकता है।

ध्यान दें कि सेवा कर्मी केवल एकल मूल के हैं और केवल https कनेक्शन या लोकलहोस्ट के माध्यम से सेवा प्राप्त वेबसाइटों पर स्थापित किए जाएंगे।

caniuse.com पर सेवा कर्मियों के लिए ब्राउज़र समर्थन के बारे में और जानें।