Pengelolaan sesi dengan pekerja layanan

Dengan Firebase Auth, pekerja layanan dapat digunakan untuk mendeteksi dan meneruskan token Firebase ID untuk pengelolaan sesi. Hal ini memberikan manfaat sebagai berikut:

  • Token ID dapat diteruskan dari server pada setiap permintaan HTTP tanpa pekerjaan tambahan apa pun.
  • Token ID dapat di-refresh tanpa tambahan latensi atau traffic.
  • Sesi backend dan frontend yang telah disinkronkan. Aplikasi yang memerlukan akses layanan Firebase, seperti Realtime Database, Firestore, dll serta beberapa resource sisi server eksternal (database SQL, dll) dapat menggunakan solusi ini. Selain itu, sesi yang sama juga dapat diakses dari pekerja layanan, pekerja web, atau pekerja bersama.
  • Menghilangkan kebutuhan untuk menyertakan kode sumber Firebase Auth pada setiap halaman (mengurangi latensi). Pekerja layanan, yang dimuat dan diinisialisasi satu kali, akan menangani pengelolaan sesi untuk semua klien di background.

Ringkasan

Firebase Auth dioptimalkan agar bisa dijalankan di sisi klien. Token disimpan di penyimpanan web. Hal ini juga memudahkan untuk berintegrasi dengan layanan Firebase lainnya, seperti Realtime Database, Cloud Firestore, Cloud Storage, dll. Untuk mengelola sesi dari perspektif sisi server, token ID harus diambil dan diteruskan ke server.

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

Dengan kata lain, beberapa skrip harus dijalankan dari sisi klien untuk mendapatkan token ID terbaru, lalu meneruskannya ke server melalui header permintaan, isi POST, dll.

Tindakan ini mungkin tidak terskalakan dan memerlukan cookie sesi sisi server. Token ID dapat ditetapkan sebagai cookie sesi, tetapi ini hanya bersifat sementara dan harus di-refresh dari sisi klien. Kemudian, token ID harus dietapkan sebagai cookie baru saat masa berlakunya sudah habis. Tindakan ini mungkin memerlukan traffic tambahan jika pengguna belum mengunjungi situs tersebut dalam beberapa lama.

Meskipun Firebase Auth menyediakan solusi pengelolaan sesi berbasis cookie yang lebih tradisional, solusi ini berfungsi dengan sangat baik untuk aplikasi berbasis cookie httpOnly sisi server dan lebih sulit dikelola karena token klien dan token sisi server dapat menjadi tidak sinkron, terutama jika Anda juga harus menggunakan layanan Firebase berbasis klien lainnya.

Akan tetapi, pekerja layanan dapat digunakan untuk mengelola sesi pengguna untuk konsumsi sisi server. Hal ini berfungsi karena hal-hal berikut:

  • Pekerja layanan dapat mengakses status Firebase Auth saat ini. Token ID pengguna saat ini dapat diambil dari pekerja layanan. Jika masa berlaku token telah habis, SDK klien akan me-refresh dan menampilkan token yang baru.
  • Pekerja layanan dapat menghalangi permintaan pengambilan dan mengubahnya.

Perubahan pekerja layanan

Pekerja layanan harus menyertakan library Auth dan kemampuan untuk mendapatkan token ID saat ini jika pengguna login.

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

Semua permintaan pengambilan yang diajukan ke asal aplikasi akan dihalangi dan akan ditambahkan ke permintaan melalui header jika token ID tersedia. Sisi server diverifikasi dan diproses (header permintaan akan diperiksa untuk token ID). Dalam skrip pekerja layanan, permintaan pengambilan akan dihalangi dan diubah.

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

self.addEventListener('fetch', (event) => {
  const requestProcessor = (idToken) => {
    let req = event.request;
    // For same origin https requests, append idToken to header.
    if (self.location.origin == getOriginFromUrl(event.request.url) &&
        (self.location.protocol == 'https:' ||
         self.location.hostname == 'localhost') &&
        idToken) {
      // Clone headers as request headers are immutable.
      const headers = new Headers();
      for (let entry of req.headers.entries()) {
        headers.append(entry[0], entry[1]);
      }
      // Add ID token to header.
      headers.append('Authorization', 'Bearer ' + idToken);
      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: req.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 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.
  event.respondWith(getIdToken().then(requestProcessor, requestProcessor));
});

Akibatnya, semua permintaan yang diautentikasi akan selalu memiliki token ID yang diteruskan di header tanpa pemrosesan tambahan.

Pekerja layanan harus diinstal secara khusus di halaman login/pendaftaran agar dapat mendeteksi perubahan status Auth. Setelah diinstal, pekerja layanan harus memanggil clients.claim() pada saat aktivasi agar dapat ditetapkan sebagai pengontrol untuk halaman saat ini.

// In service worker script.
self.addEventListener('activate', event => {
  event.waitUntil(clients.claim());
});

Perubahan sisi klien

Pekerja layanan, jika didukung, harus diinstal di halaman login/pendaftaran sisi klien.

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

Ketika pengguna login dan diarahkan ke halaman lain, pekerja layanan akan dapat menyisipkan token ID di header sebelum pengalihan selesai.

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

Perubahan sisi server

Kode sisi server akan dapat mendeteksi token ID pada setiap permintaan. Hal ini digambarkan dalam kode contoh Node.js Express berikut.

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

Kesimpulan

Harap diperhatikan bahwa token ID akan ditetapkan melalui pekerja layanan dan pekerja layanan tidak dapat dijalankan dari asal yang sama. Oleh karena itu, risiko CSRF tidak akan muncul karena situs dengan asal berbeda yang mencoba menghubungi endpoint Anda akan gagal untuk mengaktifkan pekerja layanan dan menyebabkan permintaan yang tidak terautentikasi muncul dari perspektif server.

Kini, beberapa pekerja layanan didukung di semua browser utama modern, sementara beberapa browser lama tidak mendukungnya. Akibatnya, beberapa penggantian mungkin diperlukan untuk meneruskan token ID ke server Anda ketika pekerja layanan tidak tersedia, atau aplikasi dapat dibatasi untuk berjalan di browser yang mendukung pekerja layanan saja.

Perhatikan bahwa pekerja layanan merupakan asal yang bersifat tunggal saja dan hanya akan diinstal di situs yang dilayani melalui koneksi https atau localhost.

Pelajari lebih lanjut tentang dukungan browser untuk pekerja layanan di caniuse.com.

Kirim masukan tentang...

Butuh bantuan? Kunjungi halaman dukungan kami.