Xác thực với Firebase bằng cách sử dụng liên kết email trong JavaScript

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

Có rất nhiều lợi ích khi đăng nhập bằng email:

  • Đăng ký và đăng nhập ít ma sát.
  • Giảm nguy cơ tái sử dụng mật khẩu trên các ứ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 lọc kỹ càng.
  • Khả năng xác thực người dùng trong khi cũng xác minh rằng người dùng là chủ sở hữu hợp pháp của địa chỉ email.
  • Người dùng chỉ cần một tài khoản email có thể truy cập để đăng nhập. Không yêu cầu quyền 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 an toàn mà không cần cung cấp (hoặc nhớ) mật khẩu, mật khẩu này có thể phức tạp trên thiết bị di động.
  • Người dùng hiện có trước đây đã đăng nhập bằng số nhận dạng email (mật khẩu hoặc liên kết) có thể được nâng cấp để đăng nhập chỉ 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 có, 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ư được mô tả trong Thêm Firebase vào dự án JavaScript của bạn .

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

  1. Trong bảng điều khiển Firebase , hãy mở phần Xác thực.
  2. Trên tab Phương thức đăng nhập , hãy bật nhà cung cấp Email / Mật khẩu . Lưu ý rằng đăng nhập email / mật khẩu phải được bật để sử dụng đăng nhập liên kết email.
  3. Trong cùng phần này, hãy bật phương pháp đăng nhập liên kết 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 trình bày 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ọ và sau đó gọi sendSignInLinkToEmail để yêu cầu Firebase gửi liên kết xác thực tới email của người dùng.

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

    • url : Liên kết sâu cần nhúng và bất kỳ trạng thái bổ sung nào sẽ được chuyển qua. Miền của liên kết phải được thêm vào danh sách miền được ủy quyền của Bảng điều khiển Firebase, bạn có thể tìm thấy miền này bằng cách chuyển đến tab Phương thức đăng nhập (Xác thực -> Phương thức đăng nhập).
    • androidios : Các ứng dụng sẽ sử dụng khi liên kết đăng nhập được mở trên thiết bị Android hoặc Apple. Tìm hiểu thêm về cách định cấu hình Liên kết động Firebase để mở liên kết tác vụ email qua ứng dụng dành cho thiết bị di động.
    • handleCodeInApp : Đặt thành true. Thao tác đăng nhập phải luôn được hoàn tất trong ứng dụng không giống như các thao tác email ngoài phạm vi khác (đặ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 phải đăng nhập và trạng thái Xác thực của họ vẫn tồn tại trong ứng dụng.
    • dynamicLinkDomain : Khi nhiều miền liên kết động tùy chỉnh được xác định cho một dự án, hãy chỉ định miền nào sẽ sử dụng khi liên kết được mở thông qua một ứng dụng dành cho thiết bị di động cụ thể (ví dụ: example.page.link ). Nếu không, miền đầu tiên sẽ tự động được chọn.

      Web version 9

      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 version 8

      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 Trạng thái chuyển trong phần Tác vụ email .

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

  3. Gửi 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 đăng nhập email trên cùng một thiết bị.

    Web version 9

    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 version 8

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

Lo ngại về bảo mật

Để ngăn một liên kết đăng nhập được sử dụng để đăng nhập với tư cách 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 cung cấp địa chỉ email của người dùng 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à liên kết đăng nhập được gửi ban đầu.

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

Sau khi hoàn tất đăng nhập, mọi cơ chế đăng nhập chưa được xác minh trước đó sẽ bị xóa khỏi người dùng và mọi phiên hiện có sẽ bị vô hiệu. Ví dụ: nếu trước đây ai đó đã tạo tài khoản chưa được xác minh có cùng email và mật khẩu, thì mật khẩu của người dùng sẽ bị xóa để ngă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.

Đồng thời Đảm bảo rằng bạn sử dụng URL HTTPS trong sản xuất để tránh liên kết của bạn có khả năng bị chặn bởi các máy chủ trung gian.

Hoàn tất đăng nhập vào một trang web

Định dạng của liên kết sâu liên kết email giống với định dạng được sử dụng cho các hành động email ngoài phạm vi (xác minh email, đặt lại mật khẩu và hủy bỏ thay đổi email). Firebase Auth đơn giản hóa việc kiểm tra này bằng cách cung cấp API isSignInWithEmailLink để kiểm tra xem một liên kết có phải là đăng nhập bằng liên kết 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à liên kết email thực có chứa mã một lần.

Web version 9

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

Web version 8

// 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 đăng nhập trong ứng dụng dành cho thiết bị di động

Xác thực Firebase sử dụng Liên kết động Firebase để gửi liên kết email đến thiết bị di động. Để hoàn tất đăng nhập qua ứng dụng dành cho thiết bị di động, ứng dụng phải được định cấu hình để phát hiện liên kết ứng dụng đến, phân tích cú pháp liên kết sâu bên dưới và sau đó hoàn tất quá trình đăng nhập như được thực hiện qua luồng web.

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

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

Bạn cũng có thể liên kết phương pháp xác thực này với người dùng hiện có. Ví dụ: một người dùng đã xác thực trước đó với 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ọ.

Sự khác biệt sẽ là trong nửa sau của hoạt động:

Web version 9

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 version 8

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

Điều này cũng có thể được sử dụng để xác thực lại người dùng liên kết email trước khi chạy một hoạt động nhạy cảm.

Web version 9

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 version 8

// 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, quy trình này có thể không được hoàn thành. Trong trường hợp đó, người dùng có thể hiển thị lỗi để buộc họ phải mở liên kết trên cùng một thiết bị. Một số trạng thái có thể được chuyển trong liên kết để cung cấp thông tin về loại hoạt động và người dùng.

Trong trường hợp bạn hỗ trợ cả đăng nhập bằng mật khẩu và dựa trên liên kết bằng email, để phân biệt phương pháp đăng nhập cho người dùng có mật khẩu / liên kết, hãy sử dụng fetchSignInMethodsForEmail . Điều này hữu ích cho các luồng đầu tiên nhận dạng nơi người dùng đầu tiên được yêu cầu cung cấp email của họ và sau đó được trình bày với phương thức đăng nhập:

Web version 9

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

// After asking the user for their email.
const email = window.prompt('Please provide your email');

const auth = getAuth();
fetchSignInMethodsForEmail(auth, email)
  .then((signInMethods) => {
    // This returns the same array as fetchProvidersForEmail but for email
    // provider identified by 'password' string, signInMethods would contain 2
    // different strings:
    // 'emailLink' if the user previously signed in with an email/link
    // 'password' if the user has a password.
    // A user could have both.
    if (signInMethods.indexOf(EmailAuthProvider.EMAIL_PASSWORD_SIGN_IN_METHOD) != -1) {
      // User can sign in with email/password.
    }
    if (signInMethods.indexOf(EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD) != -1) {
      // User can sign in with email/link.
    }
  })
  .catch((error) => {
    // Some error occurred, you can inspect the code: error.code
  });

Web version 8

// After asking the user for their email.
var email = window.prompt('Please provide your email');
firebase.auth().fetchSignInMethodsForEmail(email)
  .then((signInMethods) => {
    // This returns the same array as fetchProvidersForEmail but for email
    // provider identified by 'password' string, signInMethods would contain 2
    // different strings:
    // 'emailLink' if the user previously signed in with an email/link
    // 'password' if the user has a password.
    // A user could have both.
    if (signInMethods.indexOf(
            firebase.auth.EmailAuthProvider.EMAIL_PASSWORD_SIGN_IN_METHOD) != -1) {
      // User can sign in with email/password.
    }
    if (signInMethods.indexOf(
            firebase.auth.EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD) != -1) {
      // User can sign in with email/link.
    }
  })
  .catch((error) => {
    // Some error occurred, you can inspect the code: error.code
  });

Như đã mô tả ở trên, email / mật khẩu và email / liên kết được coi là cùng một firebase.auth.EmailAuthProvider (cùng PROVIDER_ID ) với các phương thức đăng nhập khác nhau.

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 — nghĩa 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 xác thực — người dùng đã đăng nhập bằng. Tài khoản mới này được lưu trữ như một phần của dự án Firebase của bạn và có thể được sử dụng để xác định người dùng trên mọi ứng dụng trong dự án của bạ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 khuyến nghị để biết trạng thái xác thực của người dùng là đặt người quan sát 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 Quản lý người dùng .

  • Trong Cơ sở dữ liệu thời gian thực Firebase và Quy tắc bảo mật lưu trữ đám mây, bạn có thể lấy ID người dùng duy nhất của người dùng đã đăng nhập từ biến auth và sử dụng nó để kiểm soát dữ liệu nào 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 mình bằng nhiều nhà cung cấp dịch vụ xác thực bằng cách liên kết thông tin xác thực của nhà cung cấp dịch vụ xác thực với tài khoản người dùng hiện có.

Để đăng xuất một người dùng, hãy gọi signOut :

Web version 9

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

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

Web version 8

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