Attiva TOTP MFA per la tua app

Questo documento descrive come aggiungere l'autenticazione a più fattori (MFA) a più fattori (TOTP) alla tua app.

Identity Platform consente di utilizzare una TOTP come fattore aggiuntivo per l'MFA. Quando attivi questa funzionalità, gli utenti che tentano di accedere alla tua app vedono una richiesta di una TOTP. Per generarlo, devono utilizzare un'app di autenticazione in grado di generare codici TOTP validi, come Google Authenticator.

Prima di iniziare

  1. Abilita almeno un provider che supporta MFA. Tieni presente che tutti i provider tranne i seguenti supportano MFA:

    • Autorizzazione telefono
    • Autenticazione anonima
    • Token di autenticazione personalizzati
    • Game Center di Apple
  2. Assicurati che l'app verifichi gli indirizzi email degli utenti. MFA richiede la verifica email. In questo modo, i malintenzionati non possono registrarsi a un servizio con un indirizzo email non di loro proprietà e quindi bloccare l'effettivo proprietario dell'indirizzo email aggiungendo un secondo fattore.

  3. Assicurati di avere la versione corretta della piattaforma. TOTP MFA è supportata solo sulle seguenti versioni dell'SDK:

    Piattaforma Versioni
    SDK web Versione 9.19.1 e successive
    SDK Android 22.1.0+
    SDK iOS 10.12.0+

Abilita TOTP MFA a livello di progetto

Per abilitare TOTP come secondo fattore, utilizza l'SDK Admin o chiama l'endpoint REST di configurazione del progetto.

Per utilizzare SDK Admin, segui questi passaggi:

  1. Se non lo hai già fatto, installa l'SDK Node.js di Firebase Admin.

    TOTP MFA è supportata solo sull'SDK Node.js Firebase Admin 11.6.0 e versioni successive.

  2. Esegui questo comando:

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

    Sostituisci quanto segue:

    • NUM_ADJ_INTERVALS: il numero di intervalli di finestre temporali adiacenti da cui accettare TOTP, compreso tra zero e dieci. Il valore predefinito è cinque.

      Le TOTP funzionano assicurando che quando due parti (il provare e lo strumento di convalida) generano OTP nella stessa finestra temporale (in genere di 30 secondi), generino la stessa password. Tuttavia, per tenere conto della deviazione dell'orologio tra le parti e dei tempi di risposta da parte di persone fisiche, puoi configurare il servizio TOTP in modo che accetti anche TOTP da finestre adiacenti.

Per abilitare TOTP MFA utilizzando l'API REST, esegui questo comando:

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

Sostituisci quanto segue:

  • PROJECT_ID: l'ID del progetto.
  • NUM_ADJ_INTERVALS: il numero di intervalli di finestre temporali, da zero a dieci. Il valore predefinito è cinque.

    Le TOTP funzionano assicurando che quando due parti (il provare e lo strumento di convalida) generano OTP nella stessa finestra temporale (in genere di 30 secondi), generino la stessa password. Tuttavia, per tenere conto della deviazione dell'orologio tra le parti e dei tempi di risposta da parte di persone fisiche, puoi configurare il servizio TOTP in modo che accetti anche TOTP da finestre adiacenti.

Abilita TOTP MFA a livello di tenant

Per abilitare la TOTP come secondo fattore per l'MFA a livello di tenant, utilizza il seguente codice:

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

Sostituisci quanto segue:

  • TENANT_ID: l'ID tenant della stringa.
  • NUM_ADJ_INTERVALS: il numero di intervalli di finestre temporali, da zero a dieci. Il valore predefinito è cinque.

Per abilitare TOTP MFA utilizzando l'API REST a livello di tenant, esegui questo comando:

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": [{
            "totpProviderConfig": {
              "adjacentIntervals": NUM_ADJ_INTERVALS
            }
          }]
       }
    }'

Sostituisci quanto segue:

  • PROJECT_ID: l'ID del progetto.
  • TENANT_ID: l'ID tenant.
  • NUM_ADJ_INTERVALS: il numero di intervalli di finestre temporali, da zero a dieci. Il valore predefinito è cinque.

Scegli un pattern di registrazione

Puoi scegliere se la tua app richiede l'autenticazione a più fattori e come e quando registrare i tuoi utenti. Ecco alcuni pattern comuni:

  • Registrare il secondo fattore dell'utente nell'ambito della registrazione. Utilizza questo metodo se la tua app richiede l'autenticazione a più fattori per tutti gli utenti.

  • Offri un'opzione ignorabile per registrare un secondo fattore durante la registrazione. Se vuoi incoraggiare l'autenticazione a più fattori nella tua app, ma non richiederla, ti consigliamo di utilizzare questo approccio.

  • Consente di aggiungere un secondo fattore dalla pagina di gestione dell'account o del profilo dell'utente, anziché dalla schermata di registrazione. In questo modo si riduce al minimo l'attrito durante il processo di registrazione, rendendo comunque disponibile l'autenticazione a più fattori per gli utenti sensibili alla sicurezza.

  • Richiedono l'aggiunta di un secondo fattore in modo incrementale quando l'utente vuole accedere a funzionalità con requisiti di sicurezza aumentati.

Iscrivere utenti a TOTP MFA

Dopo aver abilitato TOTP MFA come secondo fattore per la tua app, implementa la logica lato client per registrare gli utenti per la TOTP MFA:

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) {
      // ...

    }];
  }];
}];

Consentire agli utenti di eseguire l'accesso con un secondo fattore

Per consentire agli utenti di eseguire l'accesso con TOTP MFA, utilizza il seguente codice:

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

L'esempio precedente utilizza e-mail e password come primo fattore.

Annullamento registrazione a TOTP MFA

Questa sezione descrive come gestire l'annullamento della registrazione di un utente alla MFA TOTP.

Se un utente ha eseguito la registrazione per più opzioni MFA e se annulla la registrazione all'opzione abilitata più di recente, riceve un auth/user-token-expired e viene disconnesso. L'utente deve accedere nuovamente e verificare le proprie credenziali esistenti, ad esempio un indirizzo email e una password.

Per annullare la registrazione dell'utente, gestire l'errore e attivare la riautenticazione, utilizza il seguente codice:

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

Passaggi successivi