TOTP MFA für Ihre Anwendung aktivieren

In diesem Dokument wird beschrieben, wie Sie Ihrer Anwendung eine TOTP-Multi-Faktor-Authentifizierung (MFA) mit zeitbasiertem Einmalpasswort hinzufügen.

Mit der Identity Platform können Sie ein TOTP als zusätzlichen Faktor für MFA verwenden. Wenn Sie diese Funktion aktivieren, wird Nutzern, die sich in Ihrer Anwendung anmelden möchten, eine TOTP-Anfrage angezeigt. Um sie zu generieren, müssen sie eine Authenticator-App verwenden, die gültige TOTP-Codes generieren kann, z. B. Google Authenticator.

Hinweise

  1. Aktivieren Sie mindestens einen Anbieter, der MFA unterstützt. Alle Anbieter außer den folgenden unterstützen MFA:

    • Telefonauthentifizierung
    • Anonyme Authentifizierung
    • Benutzerdefinierte Authentifizierungstokens
    • Apple Game Center
  2. Achten Sie darauf, dass Ihre Anwendung die E-Mail-Adressen von Nutzern bestätigt. Für die MFA ist eine E-Mail-Bestätigung erforderlich. Dadurch wird verhindert, dass böswillige Akteure sich mit einer E-Mail-Adresse registrieren, die ihnen nicht gehört, und dann den tatsächlichen Inhaber der E-Mail-Adresse sperren, indem sie einen zweiten Faktor hinzufügen.

  3. Prüfen Sie, ob Sie die richtige Plattformversion verwenden. TOTP MFA wird nur in den folgenden SDK-Versionen unterstützt:

    Plattform Versionen
    Web-SDK v9.19.1 oder höher
    Android SDK 22.1.0+
    iOS SDK 10.12.0+

TOTP MFA auf Projektebene aktivieren

Verwenden Sie das Admin SDK oder rufen Sie den REST-Endpunkt für die Projektkonfiguration auf, um TOTP als zweiten Faktor zu aktivieren.

So verwenden Sie das Admin SDK:

  1. Installieren Sie das Firebase Admin Node.js SDK, falls noch nicht geschehen.

    TOTP MFA wird nur in der Firebase Admin Node.js SDK ab Version 11.6.0 unterstützt.

  2. Führen Sie den folgenden Befehl aus:

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

    Ersetzen Sie Folgendes:

    • NUM_ADJ_INTERVALS: Die Anzahl der benachbarten Zeitfensterintervalle, für die TOTPs akzeptiert werden sollen (von null bis zehn). Der Standardwert ist fünf.

      TOTPs sorgen dafür, dass, wenn zwei Parteien (der Prover und der Validator) OTPs innerhalb desselben Zeitfensters (in der Regel 30 Sekunden lang) generieren, dasselbe Passwort generieren. Um jedoch Taktabweichungen zwischen Parteien und Reaktionszeit durch Menschen zu berücksichtigen, können Sie den TOTP-Dienst so konfigurieren, dass er auch TOTPs von benachbarten Fenstern akzeptiert.

Führen Sie folgenden Befehl aus, um TOTP MFA mithilfe der REST API zu aktivieren:

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"
            }
          }]
       }
    }'

Ersetzen Sie Folgendes:

  • PROJECT_ID: die Projekt-ID
  • NUM_ADJ_INTERVALS: Die Anzahl der Zeitfensterintervalle von null bis zehn. Der Standardwert ist fünf.

    TOTPs sorgen dafür, dass, wenn zwei Parteien (der Prover und der Validator) OTPs innerhalb desselben Zeitfensters (in der Regel 30 Sekunden lang) generieren, dasselbe Passwort generieren. Um jedoch Taktabweichungen zwischen Parteien und Reaktionszeit durch Menschen zu berücksichtigen, können Sie den TOTP-Dienst so konfigurieren, dass er auch TOTPs von benachbarten Fenstern akzeptiert.

TOTP-MFA auf Mandantenebene aktivieren

Verwenden Sie den folgenden Code, um TOTP als zweiten Faktor für MFA auf Mandantenebene zu aktivieren:

getAuth().tenantManager().updateTenant(TENANT_ID,
{
      multiFactorConfig: {
          providerConfigs: [{
              state: "ENABLED",
              totpProviderConfig: {
                  adjacentIntervals: {
                      NUM_ADJ_INTERVALS
                  },
              }
          }]
      }
})

Ersetzen Sie Folgendes:

  • TENANT_ID: die String-Mandanten-ID.
  • NUM_ADJ_INTERVALS: Die Anzahl der Zeitfensterintervalle von null bis zehn. Der Standardwert ist fünf.

Führen Sie folgenden Befehl aus, um TOTP MFA mithilfe der REST API auf Mandantenebene zu aktivieren:

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

Ersetzen Sie Folgendes:

  • PROJECT_ID: die Projekt-ID
  • TENANT_ID: Die Mandanten-ID.
  • NUM_ADJ_INTERVALS: Die Anzahl der Zeitfensterintervalle von null bis zehn. Der Standardwert ist fünf.

Registrierungsmuster auswählen

Sie können auswählen, ob Ihre Anwendung eine Multi-Faktor-Authentifizierung erfordert, und wie und wann Ihre Nutzer registriert werden sollen. Einige gängige Muster sind:

  • Bieten Sie den zweiten Faktor des Nutzers als Teil der Registrierung an. Verwenden Sie diese Methode, wenn Ihre Anwendung eine Multi-Faktor-Authentifizierung für alle Nutzer erfordert.

  • Bieten Sie bei der Registrierung eine überspringbare Option an, mit der ein zweiter Faktor registriert werden kann. Wenn Sie die Multi-Faktor-Authentifizierung in Ihrer Anwendung fördern, aber nicht voraussetzen möchten, können Sie diesen Ansatz verwenden.

  • Möglichkeit bieten, einen zweiten Faktor von der Konto- oder Profilverwaltungsseite des Nutzers anstelle des Anmeldebildschirms hinzuzufügen. Dadurch wird der Registrierungsprozess vereinfacht und gleichzeitig die Multi-Faktor-Authentifizierung für sicherheitsorientierte Nutzer verfügbar gemacht.

  • Das Hinzufügen eines zweiten Faktors ist erforderlich, wenn der Nutzer auf Features mit höheren Sicherheitsanforderungen zugreifen möchte.

Nutzer für TOTP MFA registrieren

Nachdem Sie TOTP MFA als zweiten Faktor für Ihre Anwendung aktiviert haben, implementieren Sie die clientseitige Logik, um Nutzer bei TOTP MFA zu registrieren:

Web

import {
    multiFactor,
    TotpMultiFactorGenerator,
    TotpSecret
} from "firebase/auth";

multiFactorSession = await multiFactor(activeUser()).getSession();
totpSecret = await TotpMultiFactorGenerator.generateSecret(
    multiFactorSession
);

// Display this URL as a QR code.
const url = totpSecret.generateQrCodeUrl( <user account id> , <app name> );

// Ask the user for the verification code from the OTP app by scanning the QR
// code.
const multiFactorAssertion = TotpMultiFactorGenerator.assertionForEnrollment(
    totpSecret,
    verificationCode
);

// Finalize the enrollment.
return multiFactor(user).enroll(multiFactorAssertion, mfaDisplayName);

Java

user.getMultiFactor().getSession()
  .addOnCompleteListener(
  new OnCompleteListener<MultiFactorSession>() {
      @Override
      public void onComplete(@NonNull Task<MultiFactorSession> task) {
        if (task.isSuccessful()) {

          // Get a multi-factor session for the user.
          MultiFactorSession multiFactorSession = task.getResult();

          TotpMultiFactorGenerator.generateSecret(multiFactorSession)
              .addOnCompleteListener(
              new OnCompleteListener<TotpSecret>() {
                  @Override
                  public void onComplete(@NonNull Task<TotpSecret> task){
                    if (task.isSuccessful()) {

                      TotpSecret secret = task.getResult();

                      // Display this URL as a QR code for the user to scan.
                      String qrCodeUrl = secret.generateQrCodeUrl();

                      // Display the QR code
                      // ...

                      // Alternatively, you can automatically load the QR code
                      // into a TOTP authenticator app with either default or
                      // specified fallback URL and activity.
                      // Default fallback URL and activity.
                      secret.openInOtpApp(qrCodeUrl);

                      // Specified fallback URL and activity.
                      // secret.openInOtpApp(qrCodeUrl, fallbackUrl, activity);

                    }
                  }
              });
        }
      }
    });

  // Ask the user for the one-time password (otp) from the TOTP authenticator app.
  MultiFactorAssertion multiFactorAssertion =
    TotpMultiFactorGenerator.getAssertionForEnrollment(
    secret, otp);

  // Complete the enrollment.
  user
    .getMultiFactor()
    .enroll(multiFactorAssertion, /* displayName= */ "My TOTP second factor")
    .addOnCompleteListener(
      new OnCompleteListener<Void>() {
        @Override
        public void onComplete(@NonNull Task<Void> task) {
          if (task.isSuccessful()) {
            showToast("Successfully enrolled TOTP second factor!");
            setResult(Activity.RESULT_OK);
            finish();
          }
        }
    });

Kotlin+KTX

user.multiFactor.session.addOnCompleteListener { task ->
  if (task.isSuccessful) {

    // Get a multi-factor session for the user.
    val session: MultiFactorSession = task.result
    val secret: TotpSecret  = TotpMultiFactorGenerator.generateSecret(session)

    // Display this URL as a QR code for the user to scan.
    val qrCodeUrl = secret.generateQrCodeUrl()
    // Display the QR code
    // ...

    // Alternatively, you can automatically load the QR code
    // into a TOTP authenticator app with either default or
    // specified fallback URL and activity.
    // Default fallback URL and activity.
    secret.openInOtpApp(qrCodeUrl)

    // Specify a fallback URL and activity.
    // secret.openInOtpApp(qrCodeUrl, fallbackUrl, activity);
  }
}

// Ask the user for the one-time password (otp) from the TOTP authenticator app.
val multiFactorAssertion =
  TotpMultiFactorGenerator.getAssertionForEnrollment(secret, otp)

// Complete enrollment.
user.multiFactor.enroll(multiFactorAssertion, /* displayName= */ "My TOTP second factor")
  .addOnCompleteListener {
  // ...
}

Swift

let user = Auth.auth().currentUser

// Get a multi-factor session for the user
user?.multiFactor.getSessionWithCompletion({ (session, error) in
  TOTPMultiFactorGenerator.generateSecret(with: session!) {
    (secret, error) in
    let accountName = user?.email;
    let issuer = Auth.auth().app?.name;

    // Generate a QR code
    let qrCodeUrl = secret?.generateQRCodeURL(withAccountName: accountName!, issuer: issuer!)

    // Display the QR code
    // ...

    // Alternatively, you can automatically load the QR code
    // into a TOTP authenticator app with default fallback UR
    // and activity.
    secret?.openInOTPAppWithQRCodeURL(qrCodeUrl!);

    // Ask the user for the verification code after scanning
    let assertion = TOTPMultiFactorGenerator.assertionForEnrollment(with: secret, oneTimePassword: onetimePassword)

    // Complete the enrollment
    user?.multiFactor.enroll(with: assertion, displayName: accountName) { (error) in
      // ...
    }
  }
})

Objective-C

FIRUser *user = FIRAuth.auth.currentUser;

// Get a multi-factor session for the user
[user.multiFactor getSessionWithCompletion:^(FIRMultiFactorSession *_Nullable session, NSError *_Nullable error) {

  // ...

  [FIRTOTPMultiFactorGenerator generateSecretWithMultiFactorSession:session completion:^(FIRTOTPSecret *_Nullable secret, NSError *_Nullable error) {

    NSString *accountName = user.email;
    NSString *issuer = FIRAuth.auth.app.name;

    // Generate a QR code
    NSString *qrCodeUrl = [secret generateQRCodeURLWithAccountName:accountName issuer:issuer];

    // Display the QR code
    // ...

    // Alternatively, you can automatically load the QR code
    // into a TOTP authenticator app with default fallback URL
    // and activity.
    [secret openInOTPAppWithQRCodeURL:qrCodeUrl];

    // Ask the user for the verification code after scanning
    FIRTOTPMultiFactorAssertion *assertion = [FIRTOTPMultiFactorGenerator assertionForEnrollmentWithSecret:secret oneTimePassword:oneTimePassword];

    // Complete the enrollment
    [user.multiFactor enrollWithAssertion:assertion
                                    displayName:displayName
                                     completion:^(NSError *_Nullable error) {
      // ...

    }];
  }];
}];

Nutzer mit dem zweiten Faktor anmelden

Verwenden Sie den folgenden Code, um Nutzer mit TOTP MFA anzumelden:

Web

import {
    getAuth,
    getMultiFactorResolver,
    TotpMultiFactorGenerator,
    PhoneMultiFactorGenerator,
    signInWithEmailAndPassword
} from "firebase/auth";

const auth = getAuth();
signInWithEmailAndPassword(auth, email, password)
    .then(function(userCredential) {
      // The user is not enrolled with a second factor and is successfully
      // signed in.
      // ...
    })
    .catch(function(error) {
      if (error.code === 'auth/multi-factor-auth-required') {
          const resolver = getMultiFactorResolver(auth, error);

          // Ask the user which second factor to use.
          if (resolver.hints[selectedIndex].factorId ===
              TotpMultiFactorGenerator.FACTOR_ID) {

              // Ask the user for the OTP code from the TOTP app.
              const multiFactorAssertion = TotpMultiFactorGenerator.assertionForSignIn(resolver.hints[selectedIndex].uid, otp);

              // Finalize the sign-in.
              return resolver.resolveSignIn(multiFactorAssertion).then(function(userCredential) {
                // The user successfully signed in with the TOTP second factor.
              });
          } else if (resolver.hints[selectedIndex].factorId ===
              PhoneMultiFactorGenerator.FACTOR_ID) {
              // Handle the phone MFA.
          } else {
              // The second factor is unsupported.
          }
      }
      // Handle other errors, such as a wrong password.
      else if (error.code == 'auth/wrong-password') {
        //...
      }
    });

Java

FirebaseAuth.getInstance()
  .signInWithEmailAndPassword(email, password)
    .addOnCompleteListener(
        new OnCompleteListener<AuthResult>() {
          @Override
          public void onComplete(@NonNull Task<AuthResult> task) {
            if (task.isSuccessful()) {

              // The user is not enrolled with a second factor and is
              //  successfully signed in.
              // ...
              return;
            }
            if (task.getException() instanceof FirebaseAuthMultiFactorException) {

              // The user is a multi-factor user. Second factor challenge is required.
              FirebaseAuthMultiFactorException error =
                (FirebaseAuthMultiFactorException) task.getException();
              MultiFactorResolver multiFactorResolver = error.getResolver();

              // Display the list of enrolled second factors, user picks one (selectedIndex) from the list.
              MultiFactorInfo selectedHint = multiFactorResolver.getHints().get(selectedIndex);

              if (selectedHint.getFactorId().equals(TotpMultiFactorGenerator.FACTOR_ID)) {

                // Ask the user for the one-time password (otp) from the TOTP app.
                // Initialize a MultiFactorAssertion object with the one-time password and enrollment id.
                MultiFactorAssertion multiFactorAssertion =
                    TotpMultiFactorGenerator.getAssertionForSignIn(selectedHint.getUid(), otp);

                // Complete sign-in.
                multiFactorResolver
                    .resolveSignIn(multiFactorAssertion)
                    .addOnCompleteListener(
                        new OnCompleteListener<AuthResult>() {
                          @Override
                          public void onComplete(@NonNull Task<AuthResult> task) {
                            if (task.isSuccessful()) {
                              // User successfully signed in with the
                              // TOTP second factor.
                            }
                          }
                        });
              } else if (selectedHint.getFactorId().equals(PhoneMultiFactorGenerator.FACTOR_ID)) {
                // Handle Phone MFA.
              } else {
                // Unsupported second factor.
              }
            } else {
              // Handle other errors such as wrong password.
            }
          }
        });

Kotlin+KTX

FirebaseAuth.getInstance
  .signInWithEmailAndPassword(email, password)
    .addOnCompleteListener{ task ->
              if (task.isSuccessful) {
                // User is not enrolled with a second factor and is successfully
                // signed in.
                // ...
              }
              if (task.exception is FirebaseAuthMultiFactorException) {
                  // The user is a multi-factor user. Second factor challenge is
                  // required.
                  val multiFactorResolver:MultiFactorResolver =
                  (task.exception as FirebaseAuthMultiFactorException).resolver

                  // Display the list of enrolled second factors, user picks one (selectedIndex) from the list.
                  val selectedHint: MultiFactorInfo = multiFactorResolver.hints[selectedIndex]

                  if (selectedHint.factorId == TotpMultiFactorGenerator.FACTOR_ID) {
                      val multiFactorAssertion =
                          TotpMultiFactorGenerator.getAssertionForSignIn(selectedHint.uid, otp)

                      multiFactorResolver.resolveSignIn(multiFactorAssertion)
                    .addOnCompleteListener { task ->
                            if (task.isSuccessful) {
                                // User successfully signed in with the
                                // TOTP second factor.
                            }
                            // ...
                      }
                  } else if (selectedHint.factor == PhoneMultiFactorGenerator.FACTOR_ID) {
                      // Handle Phone MFA.
                  } else {
                      // Invalid MFA option.
                  }
              } else {
                  // Handle other errors, such as wrong password.
              }
          }

Swift

Auth.auth().signIn(withEmail: email, password: password) {
  (result, error) in
  if (error != nil) {
    let authError = error! as NSError
    if authError.code == AuthErrorCode.secondFactorRequired.rawValue {
      let resolver = authError.userInfo[AuthErrorUserInfoMultiFactorResolverKey] as! MultiFactorResolver
      if resolver.hints[selectedIndex].factorID == TOTPMultiFactorID {
        let assertion = TOTPMultiFactorGenerator.assertionForSignIn(withEnrollmentID: resolver.hints[selectedIndex].uid, oneTimePassword: oneTimePassword)
        resolver.resolveSignIn(with: assertion) {
          (authResult, error) in
          if (error != nil) {
            // User successfully signed in with second factor TOTP.
          }
        }

      } else if (resolver.hints[selectedIndex].factorID == PhoneMultiFactorID) {
        // User selected a phone second factor.
        // ...
      } else {
        // Unsupported second factor.
        // Note that only phone and TOTP second factors are currently supported.
        // ...
      }
    }
  }
  else {
    // The user is not enrolled with a second factor and is
    //  successfully signed in.
    // ...
  }
}

Objective-C

[FIRAuth.auth signInWithEmail:email
                   password:password
                 completion:^(FIRAuthDataResult * _Nullable authResult,
                              NSError * _Nullable error) {
  if (error == nil || error.code != FIRAuthErrorCodeSecondFactorRequired) {
      // User is not enrolled with a second factor and is successfully signed in.
      // ...
  } else {
      // The user is a multi-factor user. Second factor challenge is required.
      [self signInWithMfaWithError:error];
  }
}];

- (void)signInWithMfaWithError:(NSError * _Nullable)error{
  FIRMultiFactorResolver *resolver = error.userInfo[FIRAuthErrorUserInfoMultiFactorResolverKey];

  // Ask user which second factor to use. Then:
  FIRMultiFactorInfo *hint = (FIRMultiFactorInfo *) resolver.hints[selectedIndex];
  if (hint.factorID == FIRTOTPMultiFactorID) {
    // User selected a totp second factor.
    // Ask user for verification code.
    FIRMultiFactorAssertion *assertion = [FIRTOTPMultiFactorGenerator  assertionForSignInWithEnrollmentID:hint.UID oneTimePassword:oneTimePassword];
    [resolver resolveSignInWithAssertion:assertion
                                  completion:^(FIRAuthDataResult *_Nullable authResult,
                                              NSError *_Nullable error) {
      if (error != nil) {
        // User successfully signed in with the second factor TOTP.
      }
    }];
  } else if (hint.factorID == FIRPhoneMultiFactorID) {
      // User selected a phone second factor.
      // ...
  }
  else {
    // Unsupported second factor.
    // Note that only phone and totp second factors are currently supported.
  }
}

Im obigen Beispiel werden die E-Mail-Adresse und das Passwort als erster Faktor verwendet.

Von TOTP MFA abmelden

In diesem Abschnitt wird beschrieben, wie Sie mit einem Nutzer umgehen, der sich von TOTP MFA abmeldet.

Wenn sich ein Nutzer für mehrere MFA-Optionen registriert hat und sich von der zuletzt aktivierten Option abmeldet, erhält er ein auth/user-token-expired und wird abgemeldet. Der Nutzer muss sich noch einmal anmelden und seine vorhandenen Anmeldedaten bestätigen, z. B. seine E-Mail-Adresse und sein Passwort.

Verwenden Sie den folgenden Code, um die Registrierung des Nutzers aufzuheben, den Fehler zu beheben und die erneute Authentifizierung auszulösen:

Web

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

Java

List<MultiFactorInfo> multiFactorInfoList = user.getMultiFactor().getEnrolledFactors();

// Select the second factor to unenroll
user
  .getMultiFactor()
  .unenroll(selectedMultiFactorInfo)
  .addOnCompleteListener(
      new OnCompleteListener<Void>() {
        @Override
        public void onComplete(@NonNull Task<Void> task) {
            if (task.isSuccessful()) {
              // User successfully unenrolled the selected second factor.
            }
            else {
              if (task.getException() instanceof FirebaseAuthInvalidUserException) {
                // Handle reauthentication
              }
            }
        }
      });

Kotlin+KTX

val multiFactorInfoList = user.multiFactor.enrolledFactors

// Select the option to unenroll
user.multiFactor.unenroll(selectedMultiFactorInfo)
  .addOnCompleteListener { task ->
    if (task.isSuccessful) {
      // User successfully unenrolled the selected second factor.
    }
    else {
      if (task.exception is FirebaseAuthInvalidUserException) {
        // Handle reauthentication
      }
    }
  }

Swift

user?.multiFactor.unenroll(with: (user?.multiFactor.enrolledFactors[selectedIndex])!,
  completion: { (error) in
  if (error.code == AuthErrorCode.userTokenExpired.rawValue) {
    // Handle reauthentication
  }
})

Objective-C

FIRMultiFactorInfo *unenrolledFactorInfo;
    for (FIRMultiFactorInfo *enrolledFactorInfo in FIRAuth.auth.currentUser.multiFactor.enrolledFactors) {
      // Pick one of the enrolled factors to delete.
    }
    [FIRAuth.auth.currentUser.multiFactor unenrollWithInfo:unenrolledFactorInfo
                                          completion:^(NSError * _Nullable error) {
      if (error.code == FIRAuthErrorCodeUserTokenExpired) {
            // Handle reauthentication
        }
    }];

Nächste Schritte