Melakukan Autentikasi dengan Firebase Menggunakan Link Email di JavaScript

Anda dapat menggunakan Firebase Authentication untuk membuat pengguna login dengan mengirimkan email yang berisi link, yang dapat mereka klik untuk login. Dalam prosesnya, alamat email pengguna juga akan diverifikasi.

Ada banyak manfaat login dengan email:

  • Proses pendaftaran dan login lebih lancar.
  • Risiko penggunaan ulang sandi lintas aplikasi lebih rendah. Penggunaan sandi yang sama dapat mengurangi keamanan, sekalipun sandi sudah dipilih dengan baik.
  • Dapat mengautentikasi pengguna, sekaligus memverifikasi bahwa pengguna adalah pemilik sah alamat email.
  • Pengguna hanya memerlukan akun email yang dapat diakses untuk login. Tidak diperlukan kepemilikan akun media sosial atau nomor telepon.
  • Pengguna dapat login dengan aman tanpa perlu memasukkan (atau mengingat) sandi, yang bisa merepotkan pada perangkat seluler.
  • Pengguna yang sudah ada dan pernah login dengan ID email (sandi atau penyedia identitas gabungan) dapat diupgrade agar bisa login dengan email saja. Misalnya, pengguna yang lupa sandi masih dapat login tanpa perlu mereset sandinya.

Sebelum memulai

Jika belum melakukannya, salin cuplikan inisialisasi dari Firebase console ke project Anda seperti yang dijelaskan di bagian Menambahkan Firebase ke project JavaScript.

Untuk memproses login pengguna melalui link email, Anda harus terlebih dahulu mengaktifkan metode login dengan Link email dan Penyedia email untuk project Firebase Anda:

  1. Di Firebase console, buka bagian Auth.
  2. Pada tab Sign-in method, aktifkan penyedia Email/Password. Perlu diketahui bahwa login dengan email/sandi harus diaktifkan untuk menggunakan metode login dengan link email.
  3. Di bagian yang sama, aktifkan metode login dengan Email link (passwordless sign-in).
  4. Klik Save.

Untuk memulai alur autentikasi, tampilkan antarmuka yang akan meminta pengguna memberikan alamat email, lalu panggil sendSignInLinkToEmail untuk meminta Firebase mengirimkan link autentikasi ke email pengguna tersebut.

  1. Buat objek ActionCodeSettings yang akan memberi Firebase petunjuk mengenai cara membuat link email. Tetapkan kolom berikut:

    • url: Deep link yang akan disematkan dan semua status tambahan yang akan diteruskan. Domain link harus ditambahkan dalam daftar domain yang diotorisasi di Firebase Console, yang dapat ditemukan dengan membuka tab Sign-in method (Authentication -> Settings).
    • android dan ios: Aplikasi yang akan digunakan saat link login dibuka di perangkat Android atau Apple. Pelajari lebih lanjut cara mengonfigurasi Firebase Dynamic Links untuk membuka link tindakan email melalui aplikasi seluler.
    • handleCodeInApp: Tetapkan ke true. Operasi login harus selalu diselesaikan di aplikasi, tidak seperti tindakan email tidak umum yang lain (reset sandi dan verifikasi email). Hal ini perlu dilakukan karena di akhir alur, pengguna diharapkan sudah login dan status Auth-nya tersimpan di dalam aplikasi.
    • dynamicLinkDomain: Jika beberapa domain link dinamis kustom ditetapkan untuk suatu project, tentukan domain yang akan digunakan ketika link dibuka melalui aplikasi seluler tertentu (misalnya, example.page.link). Jika tidak ada yang ditentukan, domain pertama akan dipilih secara otomatis.

      Web

      const actionCodeSettings = {
        // URL you want to redirect back to. The domain (www.example.com) for this
        // URL must be in the authorized domains list in the Firebase Console.
        url: 'https://www.example.com/finishSignUp?cartId=1234',
        // This must be true.
        handleCodeInApp: true,
        iOS: {
          bundleId: 'com.example.ios'
        },
        android: {
          packageName: 'com.example.android',
          installApp: true,
          minimumVersion: '12'
        },
        dynamicLinkDomain: 'example.page.link'
      };

      Web

      var actionCodeSettings = {
        // URL you want to redirect back to. The domain (www.example.com) for this
        // URL must be in the authorized domains list in the Firebase Console.
        url: 'https://www.example.com/finishSignUp?cartId=1234',
        // This must be true.
        handleCodeInApp: true,
        iOS: {
          bundleId: 'com.example.ios'
        },
        android: {
          packageName: 'com.example.android',
          installApp: true,
          minimumVersion: '12'
        },
        dynamicLinkDomain: 'example.page.link'
      };

    Untuk mempelajari ActionCodeSettings lebih lanjut, baca bagian Meneruskan Status dalam Tindakan Email.

  2. Minta pengguna memberikan alamat emailnya.

  3. Kirim link autentikasi ke email pengguna, dan simpan email tersebut untuk berjaga-jaga jika pengguna menyelesaikan proses login dengan email pada perangkat yang sama.

    Web

    import { getAuth, sendSignInLinkToEmail } from "firebase/auth";
    
    const auth = getAuth();
    sendSignInLinkToEmail(auth, email, actionCodeSettings)
      .then(() => {
        // The link was successfully sent. Inform the user.
        // Save the email locally so you don't need to ask the user for it again
        // if they open the link on the same device.
        window.localStorage.setItem('emailForSignIn', email);
        // ...
      })
      .catch((error) => {
        const errorCode = error.code;
        const errorMessage = error.message;
        // ...
      });

    Web

    firebase.auth().sendSignInLinkToEmail(email, actionCodeSettings)
      .then(() => {
        // The link was successfully sent. Inform the user.
        // Save the email locally so you don't need to ask the user for it again
        // if they open the link on the same device.
        window.localStorage.setItem('emailForSignIn', email);
        // ...
      })
      .catch((error) => {
        var errorCode = error.code;
        var errorMessage = error.message;
        // ...
      });

Masalah keamanan

Demi mencegah penggunaan link login untuk login sebagai pengguna yang tidak dimaksud atau pada perangkat yang tidak dimaksud, Firebase Auth mengharuskan pengguna memasukkan alamat emailnya saat menyelesaikan alur login. Agar proses login berhasil, alamat email ini harus sama dengan alamat yang awalnya dikirimi link login.

Anda dapat menyederhanakan alur ini untuk pengguna yang membuka link login di perangkat yang sama dengan yang digunakan untuk meminta link. Caranya adalah dengan menyimpan alamat email pengguna secara lokal, misalnya menggunakan localStorage atau cookie, saat Anda mengirimkan email login. Kemudian, gunakan alamat ini untuk menyelesaikan alur. Jangan teruskan dan gunakan kembali alamat email pengguna dalam parameter URL alihan karena tindakan ini dapat mengaktifkan injeksi sesi.

Setelah proses login selesai, semua mekanisme login sebelumnya yang tidak diverifikasi akan dihapus dari pengguna dan semua sesi yang ada menjadi tidak valid. Misalnya, jika seseorang sebelumnya membuat akun yang tidak diverifikasi dengan email dan sandi yang sama, sandi pengguna tersebut akan dihapus. Dengan begitu, peniru identitas yang mengklaim kepemilikan dan membuat akun yang tidak diverifikasi tersebut tidak akan dapat login lagi dengan email dan sandi yang tidak diverifikasi.

Selain itu, pastikan Anda menggunakan URL HTTPS dalam tahap produksi agar link tidak ditangkap oleh server perantara.

Menyelesaikan proses login di halaman web

Format deep link untuk link email sama dengan format yang digunakan untuk tindakan eksternal terkait email (verifikasi email, reset sandi, dan pembatalan perubahan email). Firebase Auth menyederhanakan pemeriksaan ini dengan menyediakan isSignInWithEmailLink API untuk memeriksa apakah sebuah link adalah link login dengan email, atau bukan.

Untuk menyelesaikan proses login di halaman landing, panggil signInWithEmailLink dengan email pengguna dan link email sebenarnya yang berisi kode sekali pakai.

Web

import { getAuth, isSignInWithEmailLink, signInWithEmailLink } from "firebase/auth";

// Confirm the link is a sign-in with email link.
const auth = getAuth();
if (isSignInWithEmailLink(auth, window.location.href)) {
  // Additional state parameters can also be passed via URL.
  // This can be used to continue the user's intended action before triggering
  // the sign-in operation.
  // Get the email if available. This should be available if the user completes
  // the flow on the same device where they started it.
  let email = window.localStorage.getItem('emailForSignIn');
  if (!email) {
    // User opened the link on a different device. To prevent session fixation
    // attacks, ask the user to provide the associated email again. For example:
    email = window.prompt('Please provide your email for confirmation');
  }
  // The client SDK will parse the code from the link for you.
  signInWithEmailLink(auth, email, window.location.href)
    .then((result) => {
      // Clear email from storage.
      window.localStorage.removeItem('emailForSignIn');
      // You can access the new user by importing getAdditionalUserInfo
      // and calling it with result:
      // getAdditionalUserInfo(result)
      // You can access the user's profile via:
      // getAdditionalUserInfo(result)?.profile
      // You can check if the user is new or existing:
      // getAdditionalUserInfo(result)?.isNewUser
    })
    .catch((error) => {
      // Some error occurred, you can inspect the code: error.code
      // Common errors could be invalid email and invalid or expired OTPs.
    });
}

Web

// Confirm the link is a sign-in with email link.
if (firebase.auth().isSignInWithEmailLink(window.location.href)) {
  // Additional state parameters can also be passed via URL.
  // This can be used to continue the user's intended action before triggering
  // the sign-in operation.
  // Get the email if available. This should be available if the user completes
  // the flow on the same device where they started it.
  var email = window.localStorage.getItem('emailForSignIn');
  if (!email) {
    // User opened the link on a different device. To prevent session fixation
    // attacks, ask the user to provide the associated email again. For example:
    email = window.prompt('Please provide your email for confirmation');
  }
  // The client SDK will parse the code from the link for you.
  firebase.auth().signInWithEmailLink(email, window.location.href)
    .then((result) => {
      // Clear email from storage.
      window.localStorage.removeItem('emailForSignIn');
      // You can access the new user via result.user
      // Additional user info profile not available via:
      // result.additionalUserInfo.profile == null
      // You can check if the user is new or existing:
      // result.additionalUserInfo.isNewUser
    })
    .catch((error) => {
      // Some error occurred, you can inspect the code: error.code
      // Common errors could be invalid email and invalid or expired OTPs.
    });
}

Menyelesaikan proses login di aplikasi seluler

Firebase Authentication menggunakan Firebase Dynamic Links untuk mengirim link email ke perangkat seluler. Untuk menyelesaikan proses login melalui aplikasi seluler, aplikasi harus dikonfigurasi untuk mendeteksi link aplikasi masuk, mengurai deep link yang mendasarinya, lalu menyelesaikan proses login seperti yang dilakukan melalui alur web.

Untuk mempelajari lebih lanjut cara menangani proses login dengan link email pada aplikasi Android, lihat panduan Android.

Untuk mempelajari cara menangani proses login dengan link email pada aplikasi Apple, lihat Panduan platform Apple.

Anda juga dapat menautkan metode autentikasi ini dengan pengguna yang sudah ada. Misalnya, pengguna yang sebelumnya diautentikasi dengan penyedia lain, seperti nomor telepon, dapat menambahkan metode login ini ke akunnya yang sudah ada.

Perbedaannya terletak pada paruh kedua operasi:

Web

import { getAuth, linkWithCredential, EmailAuthProvider } from "firebase/auth";

// Construct the email link credential from the current URL.
const credential = EmailAuthProvider.credentialWithLink(
  email, window.location.href);

// Link the credential to the current user.
const auth = getAuth();
linkWithCredential(auth.currentUser, credential)
  .then((usercred) => {
    // The provider is now successfully linked.
    // The phone user can now sign in with their phone number or email.
  })
  .catch((error) => {
    // Some error occurred.
  });

Web

// Construct the email link credential from the current URL.
var credential = firebase.auth.EmailAuthProvider.credentialWithLink(
  email, window.location.href);

// Link the credential to the current user.
firebase.auth().currentUser.linkWithCredential(credential)
  .then((usercred) => {
    // The provider is now successfully linked.
    // The phone user can now sign in with their phone number or email.
  })
  .catch((error) => {
    // Some error occurred.
  });

Metode ini juga dapat digunakan untuk mengautentikasi ulang pengguna link email sebelum menjalankan operasi yang sensitif.

Web

import { getAuth, reauthenticateWithCredential, EmailAuthProvider } from "firebase/auth";

// Construct the email link credential from the current URL.
const credential = EmailAuthProvider.credentialWithLink(
  email, window.location.href);

// Re-authenticate the user with this credential.
const auth = getAuth();
reauthenticateWithCredential(auth.currentUser, credential)
  .then((usercred) => {
    // The user is now successfully re-authenticated and can execute sensitive
    // operations.
  })
  .catch((error) => {
    // Some error occurred.
  });

Web

// Construct the email link credential from the current URL.
var credential = firebase.auth.EmailAuthProvider.credentialWithLink(
  email, window.location.href);

// Re-authenticate the user with this credential.
firebase.auth().currentUser.reauthenticateWithCredential(credential)
  .then((usercred) => {
    // The user is now successfully re-authenticated and can execute sensitive
    // operations.
  })
  .catch((error) => {
    // Some error occurred.
  });

Namun, karena alur login dapat berakhir di perangkat berbeda yang tidak dipakai pengguna aslinya untuk login, alur ini mungkin tidak akan diselesaikan. Dalam hal ini, pesan error dapat ditampilkan sehingga pengguna harus membuka link di perangkat yang sama. Beberapa status dapat diteruskan di link untuk memberikan informasi tentang jenis operasi dan UID pengguna.

Jika Anda membuat project pada atau setelah tanggal 15 September 2023, perlindungan enumerasi email akan diaktifkan secara default. Fitur ini meningkatkan keamanan akun pengguna project, tetapi menonaktifkan metode fetchSignInMethodsForEmail(), yang sebelumnya kami rekomendasikan untuk mengimplementasikan alur yang mendahulukan ID.

Meskipun perlindungan enumerasi email dapat dinonaktifkan di project Anda, sebaiknya Anda tidak melakukannya.

Lihat dokumentasi tentang perlindungan enumerasi email untuk mengetahui detail selengkapnya.

Template email default untuk login dengan link

Template email default menyertakan stempel waktu di subjek dan isi email sehingga email berikutnya tidak diciutkan menjadi satu rangkaian email, dengan link disembunyikan.

Template ini berlaku untuk bahasa berikut:

Kode Bahasa
ar Arab
zh-CN China (Aksara Sederhana)
zh-TW China (Aksara Tradisional)
nl Belanda
en Inggris
en-GB Inggris (Britania)
fr Prancis
de Jerman
id Indonesia
it Italia
ja Jepang
ko Korea
pl Polandia
pt-BR Portugis (Brasil)
pt-PT Portugis (Portugal)
ru Rusia
es Spanyol
es-419 Spanyol (Amerika Latin)
th Thai

Langkah berikutnya

Setelah pengguna login untuk pertama kalinya, akun pengguna baru akan dibuat dan ditautkan ke kredensial, yaitu nama pengguna dan sandi, nomor telepon, atau informasi penyedia autentikasi, yang digunakan pengguna tersebut untuk login. Akun baru ini disimpan sebagai bagian dari project Firebase Anda, dan dapat digunakan untuk mengidentifikasi pengguna di setiap aplikasi dalam project, terlepas dari cara pengguna login.

  • Di aplikasi Anda, cara yang direkomendasikan untuk mengetahui status autentikasi pengguna adalah dengan menetapkan observer pada objek Auth. Selanjutnya, Anda bisa mendapatkan informasi profil dasar pengguna dari objek User. Baca bagian Mengelola Pengguna.

  • Di Aturan Keamanan Firebase Realtime Database dan Cloud Storage, Anda bisa mendapatkan ID pengguna unik milik pengguna yang login dari variabel auth, dan menggunakannya untuk mengontrol data yang dapat diakses oleh pengguna.

Anda dapat mengizinkan pengguna untuk login ke aplikasi menggunakan beberapa penyedia autentikasi dengan menautkan kredensial penyedia autentikasi ke akun pengguna yang ada.

Untuk memproses logout pengguna, panggil signOut:

Web

import { getAuth, signOut } from "firebase/auth";

const auth = getAuth();
signOut(auth).then(() => {
  // Sign-out successful.
}).catch((error) => {
  // An error happened.
});

Web

firebase.auth().signOut().then(() => {
  // Sign-out successful.
}).catch((error) => {
  // An error happened.
});