Thêm tính năng xác thực đa yếu tố TOTP vào ứng dụng web

Nếu đã nâng cấp lên phương thức Xác thực Firebase bằng Nền tảng nhận dạng, bạn có thể thêm phương thức xác thực đa yếu tố (MFA) dựa trên thời gian dựa trên mật khẩu một lần (TOTP) vào ứng dụng của mình.

Xác thực Firebase bằng Nền tảng nhận dạng cho phép bạn sử dụng TOTP làm yếu tố bổ sung cho MFA. Khi bạn bật tính năng này, những người dùng cố gắng đăng nhập vào ứng dụng của bạn sẽ thấy một yêu cầu về TOTP. Để tạo mã xác thực, ứng dụng xác thực có khả năng tạo mã TOTP hợp lệ, chẳng hạn như Google Authenticator.

Trước khi bắt đầu

  1. Hãy bật ít nhất một nhà cung cấp hỗ trợ tính năng xác thực đa yếu tố (MFA). Xin lưu ý rằng tất cả các nhà cung cấp ngoại trừ việc xác thực đa yếu tố (MFA) hỗ trợ sau đây:

    • Xác thực điện thoại
    • Xác thực ẩn danh
    • Mã thông báo xác thực tuỳ chỉnh
    • Trung tâm trò chơi của Apple
  2. Đảm bảo ứng dụng của bạn xác minh địa chỉ email của người dùng. MFA yêu cầu xác minh email. Điều này ngăn đối tượng độc hại đăng ký dịch vụ bằng địa chỉ email mà họ không sở hữu, sau đó khoá chủ sở hữu thực sự của địa chỉ email đó bằng cách thêm yếu tố thứ hai.

  3. Nếu bạn chưa làm như vậy, hãy cài đặt SDK JavaScript của Firebase.

    TOTP MFA chỉ được hỗ trợ trên SDK web mô-đun, phiên bản v9.19.1 trở lên.

Bật TOTP MFA

Để bật TOTP làm yếu tố thứ hai, hãy sử dụng SDK dành cho quản trị viên hoặc gọi điểm cuối REST cấu hình dự án.

Để sử dụng SDK dành cho quản trị viên, hãy làm như sau:

  1. Nếu bạn chưa làm như vậy, hãy cài đặt SDK Node.js cho quản trị viên Firebase.

    TOTP MFA chỉ được hỗ trợ trên Firebase Admin Node.js SDK phiên bản 11.6.0 trở lên.

  2. Chạy lệnh sau:

    import { getAuth } from 'firebase-admin/auth';
    
    getAuth().projectConfigManager().updateProjectConfig(
    {
          multiFactorConfig: {
              providerConfigs: [{
                  state: "ENABLED",
                  totpProviderConfig: {
                      adjacentIntervals: {
                          NUM_ADJ_INTERVALS
                      },
                  }
              }]
          }
    })
    

    Thay thế các đoạn mã sau:

    • NUM_ADJ_INTERVALS: Số khoảng thời gian liền kề để chấp nhận các TOTP, từ 0 đến 10. Giá trị mặc định là 5.

      TOTP hoạt động bằng cách đảm bảo rằng khi hai bên (trình chứng minh và trình xác thực) tạo OTP trong cùng một khoảng thời gian (thường là 30 giây), họ sẽ tạo cùng một mật khẩu. Tuy nhiên, để thích ứng với tình trạng trễ thời gian giữa các bên và thời gian phản hồi của con người, bạn có thể định cấu hình dịch vụ TOTP để chấp nhận TOTP từ các cửa sổ liền kề.

Để bật TOTP MFA bằng API REST, hãy chạy như sau:

curl -X PATCH "https://identitytoolkit.googleapis.com/admin/v2/projects/PROJECT_ID/config?updateMask=mfa" \
    -H "Authorization: Bearer $(gcloud auth print-access-token)" \
    -H "Content-Type: application/json" \
    -H "X-Goog-User-Project: PROJECT_ID" \
    -d \
    '{
        "mfa": {
          "providerConfigs": [{
            "state": "ENABLED",
            "totpProviderConfig": {
              "adjacentIntervals": "NUM_ADJ_INTERVALS"
            }
          }]
       }
    }'

Thay thế các đoạn mã sau:

  • PROJECT_ID: Mã dự án.
  • NUM_ADJ_INTERVALS: Số khoảng thời gian của khung thời gian, từ 0 đến 10. Giá trị mặc định là 5.

    TOTP hoạt động bằng cách đảm bảo rằng khi hai bên (trình chứng minh và trình xác thực) tạo OTP trong cùng một khoảng thời gian (thường là 30 giây), họ sẽ tạo cùng một mật khẩu. Tuy nhiên, để thích ứng với tình trạng trễ thời gian giữa các bên và thời gian phản hồi của con người, bạn có thể định cấu hình dịch vụ TOTP để chấp nhận TOTP từ các cửa sổ liền kề.

Chọn một mẫu đăng ký

Bạn có thể chọn xem ứng dụng của mình có yêu cầu xác thực đa yếu tố hay không, cũng như cách thức và thời điểm đăng ký người dùng. Sau đây là một số kiểu mẫu phổ biến:

  • Đăng ký yếu tố thứ hai của người dùng trong quá trình đăng ký. Hãy sử dụng phương thức này nếu ứng dụng của bạn yêu cầu xác thực đa yếu tố cho tất cả người dùng.

  • Cung cấp một lựa chọn có thể bỏ qua để đăng ký yếu tố thứ hai trong quá trình đăng ký. Nếu muốn khuyến khích nhưng không yêu cầu xác thực đa yếu tố trong ứng dụng, bạn có thể sử dụng phương pháp này.

  • Cho phép thêm yếu tố thứ hai từ trang quản lý tài khoản hoặc hồ sơ của người dùng thay vì màn hình đăng ký. Điều này giúp giảm thiểu phiền hà trong quá trình đăng ký, đồng thời vẫn cung cấp tính năng xác thực đa yếu tố cho những người dùng nhạy cảm về tính bảo mật.

  • Yêu cầu thêm dần yếu tố thứ hai khi người dùng muốn truy cập các tính năng có yêu cầu cao hơn về bảo mật.

Đăng ký người dùng trong TOTP MFA

Sau khi bạn bật TOTP MFA làm yếu tố thứ hai cho ứng dụng, hãy triển khai logic phía máy khách để đăng ký người dùng trong TOTP MFA:

  1. Nhập các lớp và hàm MFA bắt buộc:

    import {
      multiFactor,
      TotpMultiFactorGenerator,
      TotpSecret,
      getAuth,
    } from "firebase/auth";
    
  2. Xác thực lại người dùng.

  3. Tạo mã bí mật TOTP cho người dùng đã xác thực:

    // Generate a TOTP secret.
    const multiFactorSession = await multiFactor(currentUser).getSession();
    const totpSecret = await TotpMultiFactorGenerator.generateSecret(
      multiFactorSession
    );
    
  4. Hiển thị bí mật cho người dùng và nhắc họ nhập mã đó vào ứng dụng xác thực.

    Với nhiều ứng dụng xác thực, người dùng có thể nhanh chóng thêm các mã bí mật TOTP mới bằng cách quét mã QR đại diện cho URI khoá tương thích với Google Authenticator. Để tạo mã QR cho mục đích này, hãy tạo URI bằng generateQrCodeUrl() rồi mã hoá bằng thư viện mã QR mà bạn chọn. Ví dụ:

    const totpUri = totpSecret.generateQrCodeUrl(
        currentUser.email,
        "Your App's Name"
    );
    await QRExampleLib.toCanvas(totpUri, qrElement);
    

    Bất kể bạn có cho thấy mã QR hay không, hãy luôn hiển thị khoá bí mật để hỗ trợ các ứng dụng xác thực không thể đọc mã QR:

    // Also display this key:
    const secret = totpSecret.secretKey;
    

    Sau khi người dùng thêm khoá bí mật vào ứng dụng xác thực, ứng dụng đó sẽ bắt đầu tạo TOTP.

  5. Nhắc người dùng nhập TOTP hiển thị trên ứng dụng xác thực và sử dụng TOTP này để hoàn tất việc đăng ký MFA:

    // Ask the user for a verification code from the authenticator app.
    const verificationCode = // Code from user input.
    
    // Finalize the enrollment.
    const multiFactorAssertion = TotpMultiFactorGenerator.assertionForEnrollment(
      totpSecret,
      verificationCode
    );
    await multiFactor(currentUser).enroll(multiFactorAssertion, mfaDisplayName);
    

Người dùng đăng nhập bằng yếu tố thứ hai

Để đăng nhập người dùng bằng TOTP MFA, hãy sử dụng mã sau:

  1. Nhập các lớp và hàm MFA bắt buộc:

    import {
        getAuth,
        getMultiFactorResolver,
        TotpMultiFactorGenerator,
    } from "firebase/auth";
    
  2. Gọi một trong các phương thức signInWith- giống như khi bạn không sử dụng MFA. (Ví dụ: signInWithEmailAndPassword().) Nếu phương thức này báo lỗi auth/multi-factor-auth-required, hãy bắt đầu luồng MFA của ứng dụng.

    try {
        const userCredential = await signInWithEmailAndPassword(
            getAuth(),
            email,
            password
        );
        // If the user is not enrolled with a second factor and provided valid
        // credentials, sign-in succeeds.
    
        // (If your app requires MFA, this could be considered an error
        // condition, which you would resolve by forcing the user to enroll a
        // second factor.)
    
        // ...
    } catch (error) {
        switch (error.code) {
            case "auth/multi-factor-auth-required":
                // Initiate your second factor sign-in flow. (See next step.)
                // ...
                break;
            case ...:  // Handle other errors, such as wrong passwords.
                break;
        }
    }
    
  3. Trước tiên, luồng MFA của ứng dụng phải nhắc người dùng chọn yếu tố thứ hai mà họ muốn sử dụng. Bạn có thể xem danh sách các yếu tố thứ hai được hỗ trợ bằng cách kiểm tra thuộc tính hints của một thực thể MultiFactorResolver:

    const mfaResolver = getMultiFactorResolver(getAuth(), error);
    const enrolledFactors = mfaResolver.hints.map(info => info.displayName);
    
  4. Nếu người dùng chọn sử dụng TOTP, hãy nhắc họ nhập TOTP hiển thị trên ứng dụng xác thực và dùng TOTP để đăng nhập:

    switch (mfaResolver.hints[selectedIndex].factorId) {
        case TotpMultiFactorGenerator.FACTOR_ID:
            const otpFromAuthenticator = // OTP typed by the user.
            const multiFactorAssertion =
                TotpMultiFactorGenerator.assertionForSignIn(
                    mfaResolver.hints[selectedIndex].uid,
                    otpFromAuthenticator
                );
            try {
                const userCredential = await mfaResolver.resolveSignIn(
                    multiFactorAssertion
                );
                // Successfully signed in!
            } catch (error) {
                // Invalid or expired OTP.
            }
            break;
        case PhoneMultiFactorGenerator.FACTOR_ID:
            // Handle SMS second factor.
            break;
        default:
            // Unsupported second factor?
            break;
    }
    

Huỷ đăng ký TOTP MFA

Phần này mô tả cách xử lý người dùng huỷ đăng ký khỏi TOTP MFA.

Nếu người dùng đã đăng ký nhiều tuỳ chọn MFA và nếu họ huỷ đăng ký tuỳ chọn bật gần đây nhất, họ sẽ nhận được một auth/user-token-expired và bị đăng xuất. Người dùng phải đăng nhập lại và xác minh thông tin đăng nhập hiện có của họ – ví dụ: địa chỉ email và mật khẩu.

Để huỷ đăng ký cho người dùng, xử lý lỗi và kích hoạt tính năng xác thực lại, hãy sử dụng đoạn mã sau:

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

try {
    // Unenroll from TOTP MFA.
    await multiFactor(currentUser).unenroll(mfaEnrollmentId);
} catch  (error) {
    if (error.code === 'auth/user-token-expired') {
        // If the user was signed out, re-authenticate them.

        // For example, if they signed in with a password, prompt them to
        // provide it again, then call `reauthenticateWithCredential()` as shown
        // below.

        const credential = EmailAuthProvider.credential(email, password);
        await reauthenticateWithCredential(
            currentUser,
            credential
        );
    }
}

Bước tiếp theo