Xác thực bằng Firebase bằng đường liên kết email trong JavaScript

Bạn có thể dùng Xác thực Firebase để đăng nhập cho người dùng bằng cách gửi cho họ một email chứa đường liên kết. Họ có thể nhấp vào đường liên kết đó để đăng nhập. Trong quá trình này, địa chỉ email của người dùng cũng được xác minh.

Việc đăng nhập bằng email mang lại nhiều lợi ích như sau:

  • Đăng ký và đăng nhập dễ dàng.
  • Giảm nguy cơ sử dụng lại mật khẩu trên nhiều ứng dụng, điều này có thể làm suy yếu tính bảo mật của ngay cả những mật khẩu được chọn kỹ lưỡng.
  • Khả năng xác thực người dùng, đồng thời xác minh rằng người dùng đó là chủ sở hữu hợp pháp của một địa chỉ email.
  • Người dùng chỉ cần có một tài khoản email có thể truy cập để đăng nhập. Bạn không cần phải sở hữu số điện thoại hoặc tài khoản mạng xã hội.
  • Người dùng có thể đăng nhập một cách an toàn mà không cần cung cấp (hoặc nhớ) mật khẩu. Điều này có thể gây phiền toái trên thiết bị di động.
  • Người dùng hiện tại từng đăng nhập bằng một giá trị nhận dạng email (mật khẩu hoặc liên kết) có thể được nâng cấp để chỉ đăng nhập bằng email. Ví dụ: người dùng quên mật khẩu vẫn có thể đăng nhập mà không cần đặt lại mật khẩu.

Trước khi bắt đầu

Nếu bạn chưa thực hiện, hãy sao chép đoạn mã khởi tạo từ bảng điều khiển Firebase vào dự án của bạn như mô tả trong phần Thêm Firebase vào dự án JavaScript.

Để đăng nhập người dùng bằng đường liên kết qua email, trước tiên, bạn phải bật Nhà cung cấp email và phương thức đăng nhập bằng đường liên kết qua email cho dự án Firebase của mình:

  1. Trong bảng điều khiển Firebase, hãy chuyển đến phần Bảo mật > Xác thực.

  2. Trong thẻ Phương thức đăng nhập, hãy bật phương thức đăng nhập Email/Mật khẩu. Xin lưu ý rằng bạn phải bật tính năng đăng nhập bằng email/mật khẩu để sử dụng tính năng đăng nhập bằng đường liên kết qua email.

  3. Trong cùng một phần, hãy bật trình cung cấp dịch vụ đăng nhập Đường liên kết qua email (đăng nhập không cần mật khẩu).

  4. Nhấp vào Lưu.

Để bắt đầu quy trình xác thực, hãy cung cấp cho người dùng một giao diện nhắc người dùng cung cấp địa chỉ email của họ, sau đó gọi sendSignInLinkToEmail để yêu cầu Firebase gửi đường liên kết xác thực đến email của người dùng.

  1. Tạo đối tượng ActionCodeSettings. Đối tượng này cung cấp cho Firebase hướng dẫn về cách tạo đường liên kết trong email. Đặt các trường sau:

    • url: Đường liên kết sâu cần nhúng và mọi trạng thái bổ sung cần được truyền đi. Nếu bạn chưa thêm miền của mình vào danh sách miền được uỷ quyền, hãy làm như sau:

      1. Trong bảng điều khiển Firebase, hãy chuyển đến thẻ Bảo mật > Xác thực > Cài đặt.

      2. Trong mục Miền được uỷ quyền, hãy nhấp vào Thêm miền rồi thêm miền của bạn.

    • android và ios: Giúp Firebase Authentication xác định xem có nên tạo một đường liên kết chỉ dành cho web hay đường liên kết dành cho thiết bị di động được mở trên thiết bị Android hoặc Apple hay không.
    • handleCodeInApp: Đặt thành true. Bạn phải luôn hoàn tất thao tác đăng nhập trong ứng dụng, không giống như các thao tác khác qua email (đặt lại mật khẩu và xác minh email). Điều này là do, ở cuối quy trình, người dùng dự kiến sẽ đăng nhập và trạng thái Auth của họ được duy trì trong ứng dụng.
    • linkDomain: Khi bạn xác định các miền liên kết Hosting tuỳ chỉnh cho một dự án, hãy chỉ định miền cần sử dụng khi một ứng dụng di động cụ thể mở đường liên kết. Nếu không, miền mặc định sẽ tự động được chọn (ví dụ: PROJECT_ID.firebaseapp.com).
    • dynamicLinkDomain: Không dùng nữa. Không chỉ định tham số này.

      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'
        },
        // The domain must be configured in Firebase Hosting and owned by the project.
        linkDomain: 'custom-domain.com'
      };

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

    Để tìm hiểu thêm về ActionCodeSettings, hãy tham khảo phần Truyền trạng thái trong các thao tác trên email.

  2. Yêu cầu người dùng cung cấp email của họ.

  3. Gửi đường liên kết xác thực đến email của người dùng và lưu email của người dùng trong trường hợp người dùng hoàn tất quy trình đăng nhập bằng email trên cùng một thiết bị.

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

Các mối lo ngại về bảo mật

Để ngăn việc sử dụng đường liên kết đăng nhập để đăng nhập với tư cách là người dùng không mong muốn hoặc trên thiết bị không mong muốn, Firebase Auth yêu cầu người dùng cung cấp địa chỉ email khi hoàn tất quy trình đăng nhập. Để đăng nhập thành công, địa chỉ email này phải khớp với địa chỉ mà đường liên kết đăng nhập được gửi đến ban đầu.

Bạn có thể đơn giản hoá quy trình này cho những người dùng mở đường liên kết đăng nhập trên cùng một thiết bị mà họ yêu cầu đường liên kết, bằng cách lưu trữ địa chỉ email của họ cục bộ (ví dụ: sử dụng localStorage hoặc cookie) khi bạn gửi email đăng nhập. Sau đó, hãy dùng địa chỉ này để hoàn tất quy trình. Không truyền email của người dùng trong các tham số URL chuyển hướng và sử dụng lại email đó vì điều này có thể cho phép chèn phiên.

Sau khi hoàn tất quá trình đăng nhập, mọi cơ chế đăng nhập chưa được xác minh trước đó sẽ bị xoá khỏi người dùng và mọi phiên hiện có sẽ mất hiệu lực. Ví dụ: nếu trước đây có người đã tạo một tài khoản chưa được xác minh bằng cùng một email và mật khẩu, thì mật khẩu của người dùng sẽ bị xoá để ngăn chặn kẻ mạo danh đã xác nhận quyền sở hữu và tạo tài khoản chưa được xác minh đó đăng nhập lại bằng email và mật khẩu chưa được xác minh.

Ngoài ra, hãy nhớ sử dụng URL HTTPS trong quá trình sản xuất để tránh trường hợp đường liên kết của bạn có thể bị các máy chủ trung gian chặn.

Hoàn tất quá trình đăng nhập trên trang web

Định dạng của đường liên kết sâu trong email giống với định dạng được dùng cho các thao tác ngoài băng tần qua email (xác minh email, đặt lại mật khẩu và thu hồi thay đổi email). Firebase Auth đơn giản hoá quy trình kiểm tra này bằng cách cung cấp API isSignInWithEmailLink để kiểm tra xem một đường liên kết có phải là đường liên kết đăng nhập bằng email hay không.

Để hoàn tất quá trình đăng nhập trên trang đích, hãy gọi signInWithEmailLink bằng email của người dùng và đường liên kết thực tế trong email có chứa mã dùng một lần.

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

Hoàn tất quy trình đăng nhập trong ứng dụng di động

Firebase Authentication sử dụng Firebase Hosting để gửi đường liên kết trong email đến một thiết bị di động. Để hoàn tất quy trình đăng nhập thông qua ứng dụng di động, bạn phải định cấu hình ứng dụng để phát hiện đường liên kết đến ứng dụng, phân tích cú pháp đường liên kết sâu cơ bản, sau đó hoàn tất quy trình đăng nhập như khi thực hiện thông qua quy trình trên web.

Để tìm hiểu thêm về cách xử lý hoạt động đăng nhập bằng đường liên kết qua email trong một ứng dụng Android, hãy tham khảo hướng dẫn dành cho Android.

Để tìm hiểu thêm về cách xử lý hoạt động đăng nhập bằng đường liên kết qua email trong một ứng dụng Apple, hãy tham khảo hướng dẫn về các nền tảng của Apple.

Bạn cũng có thể liên kết phương thức xác thực này với một người dùng hiện có. Ví dụ: người dùng đã xác thực bằng một nhà cung cấp khác (chẳng hạn như số điện thoại) có thể thêm phương thức đăng nhập này vào tài khoản hiện có của họ.

Điểm khác biệt sẽ nằm ở nửa sau của thao tác:

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

Bạn cũng có thể dùng phương thức này để xác thực lại người dùng liên kết qua email trước khi chạy một thao tác nhạy cảm.

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

Tuy nhiên, vì quy trình có thể kết thúc trên một thiết bị khác mà người dùng ban đầu chưa đăng nhập, nên quy trình này có thể không hoàn tất. Trong trường hợp đó, người dùng có thể thấy một thông báo lỗi để buộc họ mở đường liên kết trên cùng một thiết bị. Bạn có thể truyền một số trạng thái trong đường liên kết để cung cấp thông tin về loại thao tác và mã nhận dạng người dùng.

Nếu bạn tạo dự án của mình từ ngày 15 tháng 9 năm 2023 trở đi, thì tính năng bảo vệ chống liệt kê email sẽ được bật theo mặc định. Tính năng này cải thiện tính bảo mật của tài khoản người dùng trong dự án, nhưng sẽ vô hiệu hoá phương thức fetchSignInMethodsForEmail(). Trước đây, chúng tôi đề xuất bạn triển khai các luồng nhận dạng trước.

Mặc dù bạn có thể tắt tính năng bảo vệ chống liệt kê email cho dự án của mình, nhưng bạn không nên làm như vậy.

Hãy xem tài liệu về biện pháp bảo vệ chống liệt kê email để biết thêm thông tin chi tiết.

Mẫu email mặc định để đăng nhập bằng đường liên kết

Mẫu email mặc định có dấu thời gian trong tiêu đề và nội dung email để các email tiếp theo không bị thu gọn thành một chuỗi duy nhất và đường liên kết bị ẩn.

Mẫu này áp dụng cho các ngôn ngữ sau:

Mã Ngôn ngữ
ar Tiếng Ả Rập
zh-CN Tiếng Trung (Giản thể)
zh-TW Tiếng Trung (Phồn thể)
nl Tiếng Hà Lan
en Tiếng Anh
en-GB Tiếng Anh (Anh)
fr Tiếng Pháp
de Tiếng Đức
id Tiếng Indonesia
it Tiếng Ý
ja Tiếng Nhật
ko Tiếng Hàn
pl Tiếng Ba Lan
pt-BR Tiếng Bồ Đào Nha (Brazil)
pt-PT Tiếng Bồ Đào Nha (Bồ Đào Nha)
ru Tiếng Nga
es Tiếng Tây Ban Nha
es-419 Tiếng Tây Ban Nha (Mỹ Latinh)
th Tiếng Thái

Các bước tiếp theo

Sau khi người dùng đăng nhập lần đầu tiên, một tài khoản người dùng mới sẽ được tạo và liên kết với thông tin đăng nhập (tức là tên người dùng và mật khẩu, số điện thoại hoặc thông tin nhà cung cấp dịch vụ uỷ quyền) mà người dùng đã đăng nhập. Tài khoản mới này được lưu trữ trong dự án Firebase của bạn và có thể dùng để xác định một người dùng trên mọi ứng dụng trong dự án, bất kể người dùng đăng nhập bằng cách nào.

  • Trong các ứng dụng của bạn, cách được đề xuất để biết trạng thái uỷ quyền của người dùng là đặt một đối tượng theo dõi trên đối tượng Auth. Sau đó, bạn có thể lấy thông tin hồ sơ cơ bản của người dùng từ đối tượng User. Xem phần Quản lý người dùng.

  • Trong Firebase Realtime Database và Cloud Storage Quy tắc bảo mật, bạn có thể lấy mã nhận dạng người dùng riêng biệt của người dùng đã đăng nhập từ biến auth và dùng mã nhận dạng này để kiểm soát dữ liệu mà người dùng có thể truy cập.

Bạn có thể cho phép người dùng đăng nhập vào ứng dụng của bạn bằng nhiều trình cung cấp dịch vụ xác thực bằng cách liên kết thông tin đăng nhập của trình cung cấp dịch vụ xác thực với một tài khoản người dùng hiện có.

Để đăng xuất người dùng, hãy gọi 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.
});