获取我们在 Firebase 峰会上发布的所有信息,了解 Firebase 可如何帮助您加快应用开发速度并满怀信心地运行应用。了解详情

向您的 iOS 应用程序添加多因素身份验证

如果您已使用 Identity Platform 升级到 Firebase 身份验证,则可以将 SMS 多重身份验证添加到您的 iOS 应用程序。

多重身份验证可提高应用程序的安全性。虽然攻击者通常会泄露密码和社交帐户,但拦截短信却更加困难。

在你开始之前

  1. 启用至少一个支持多重身份验证的提供商。每个提供商都支持 MFA,电话身份验证、匿名身份验证和 Apple Game Center 除外。

  2. 确保您的应用正在验证用户电子邮件。 MFA 需要电子邮件验证。这可以防止恶意行为者使用不属于他们的电子邮件注册服务,然后通过添加第二个因素来锁定真正的所有者。

启用多重身份验证

  1. 打开 Firebase 控制台的身份验证 > 登录方法页面。

  2. Advanced部分,启用SMS Multi-factor Authentication

    您还应该输入您将用来测试您的应用程序的电话号码。虽然是可选的,但强烈建议注册测试电话号码以避免在开发过程中受到限制。

  3. 如果您尚未授权应用的域,请将其添加到 Firebase 控制台的“身份验证”>“设置”页面上的允许列表。

验证您的应用

Firebase 需要验证短信请求是否来自您的应用。您可以通过两种方式执行此操作:

  • 静默 APNs 通知:当您首次登录用户时,Firebase 可以向用户的设备发送静默推送通知。如果应用程序收到通知,则可以继续进行身份验证。请注意,从 iOS 8.0 开始,您无需要求用户允许推送通知即可使用此方法。

  • reCAPTCHA 验证:如果您无法发送静默通知(例如,因为用户禁用了后台刷新,或者您正在 iOS 模拟器中测试您的应用程序),您可以使用 reCAPTCHA。在许多情况下,reCAPTCHA 会自动解决,无需用户交互。

使用静默通知

要启用 APNs 通知以用于 Firebase:

  1. 在 Xcode 中,为您的项目启用推送通知

  2. 使用 Firebase 控制台上传您的 APNs 身份验证密钥(您的更改将自动转移到 Google Cloud Firebase)。如果您还没有 APNs 身份验证密钥,请参阅使用 FCM 配置 APNs以了解如何获取它。

    1. 打开Firebase 控制台

    2. 导航到项目设置

    3. 选择云消息传递选项卡。

    4. APNs authentication key下,在iOS app configuration部分,单击Upload

    5. 选择你的钥匙。

    6. 添加密钥的密钥 ID。您可以在Apple Developer Member CenterCertificates, Identifiers & Profiles下找到密钥 ID。

    7. 单击上传

如果您已经拥有 APNs 证书,则可以改为上传证书。

使用 reCAPTCHA 验证

启用客户端 SDK 以使用 reCAPTCHA:

  1. 在 Xcode 中打开您的项目配置。

  2. 双击左侧树视图中的项目名称。

  3. 从“目标”部分选择您的应用。

  4. 选择信息选项卡。

  5. 展开URL 类型部分。

  6. 单击+按钮。

  7. URL Schemes字段中输入您的反向客户端 ID。您可以在GoogleService-Info.plist配置文件中找到该值作为REVERSED_CLIENT_ID列出。

完成后,您的配置应类似于以下内容:

自定义方案

或者,您可以自定义您的应用程序在显示 reCAPTCHA 时呈现SFSafariViewControllerUIWebView的方式。为此,创建一个符合FIRAuthUIDelegate协议的自定义类,并将其传递给verifyPhoneNumber:UIDelegate:completion:

选择注册模式

您可以选择您的应用程序是否需要多重身份验证,以及如何以及何时注册您的用户。一些常见的模式包括:

  • 注册用户的第二因素作为注册的一部分。如果您的应用程序需要对所有用户进行多重身份验证,请使用此方法。请注意,帐户必须具有经过验证的电子邮件地址才能注册第二个因素,因此您的注册流程必须适应这一点。

  • 提供可跳过的选项以在注册期间注册第二个因素。想要鼓励但不要求多重身份验证的应用程序可能更喜欢这种方法。

  • 提供从用户帐户或个人资料管理页面而不是注册屏幕添加第二因素的能力。这最大限度地减少了注册过程中的摩擦,同时仍然为对安全敏感的用户提供多因素身份验证。

  • 当用户想要访问具有更高安全要求的功能时,需要逐渐添加第二个因素。

注册第二个因素

要为用户注册新的次要因素:

  1. 重新验证用户。

  2. 要求用户输入他们的电话号码。

  3. 为用户获取多因素会话:

    迅速

    authResult.user.multiFactor.getSessionWithCompletion() { (session, error) in
      // ...
    }
    

    目标-C

    [authResult.user.multiFactor
      getSessionWithCompletion:^(FIRMultiFactorSession * _Nullable session,
                                NSError * _Nullable error) {
        // ...
    }];
    
  4. 向用户的手机发送验证消息。确保电话号码的格式为前导+并且没有其他标点符号或空格(例如: +15105551234

    迅速

    // Send SMS verification code.
    PhoneAuthProvider.provider().verifyPhoneNumber(
      phoneNumber,
      uiDelegate: nil,
      multiFactorSession: session) { (verificationId, error) in
        // verificationId will be needed for enrollment completion.
    }
    

    目标-C

    // Send SMS verification code.
    [FIRPhoneAuthProvider.provider verifyPhoneNumber:phoneNumber
                                          UIDelegate:nil
                                  multiFactorSession:session
                                          completion:^(NSString * _Nullable verificationID,
                                                        NSError * _Nullable error) {
        // verificationId will be needed for enrollment completion.
    }];
    

    虽然不是必需的,但最好事先通知用户他们将收到一条 SMS 消息,并且适用标准费率。

    verifyPhoneNumber()方法使用静默推送通知在后台启动应用程序验证过程。如果静默推送通知不可用,则会发出 reCAPTCHA 质询。

  5. 发送短信代码后,要求用户验证代码。然后,使用他们的响应来构建PhoneAuthCredential

    迅速

    // Ask user for the verification code. Then:
    let credential = PhoneAuthProvider.provider().credential(
      withVerificationID: verificationId,
      verificationCode: verificationCode)
    

    目标-C

    // Ask user for the SMS verification code. Then:
    FIRPhoneAuthCredential *credential = [FIRPhoneAuthProvider.provider
                                           credentialWithVerificationID:verificationID
                                           verificationCode:kPhoneSecondFactorVerificationCode];
    
  6. 初始化断言对象:

    迅速

    let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
    

    目标-C

    FIRMultiFactorAssertion *assertion = [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];
    
  7. 完成注册。或者,您可以为第二个因素指定显示名称。这对于具有多个第二因素的用户很有用,因为电话号码在身份验证流程中被屏蔽(例如,+1******1234)。

    迅速

    // Complete enrollment. This will update the underlying tokens
    // and trigger ID token change listener.
    user.multiFactor.enroll(with: assertion, displayName: displayName) { (error) in
      // ...
    }
    

    目标-C

    // Complete enrollment. This will update the underlying tokens
    // and trigger ID token change listener.
    [authResult.user.multiFactor enrollWithAssertion:assertion
                                         displayName:nil
                                          completion:^(NSError * _Nullable error) {
        // ...
    }];
    

下面的代码显示了注册第二因素的完整示例:

迅速

let user = Auth.auth().currentUser
user?.multiFactor.getSessionWithCompletion({ (session, error) in
  // Send SMS verification code.
  PhoneAuthProvider.provider().verifyPhoneNumber(
    phoneNumber,
    uiDelegate: nil,
    multiFactorSession: session
  ) { (verificationId, error) in
    // verificationId will be needed for enrollment completion.
    // Ask user for the verification code.
    let credential = PhoneAuthProvider.provider().credential(
      withVerificationID: verificationId!,
      verificationCode: phoneSecondFactorVerificationCode)
    let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
    // Complete enrollment. This will update the underlying tokens
    // and trigger ID token change listener.
    user?.multiFactor.enroll(with: assertion, displayName: displayName) { (error) in
      // ...
    }
  }
})

目标-C

FIRUser *user = FIRAuth.auth.currentUser;
[user.multiFactor getSessionWithCompletion:^(FIRMultiFactorSession * _Nullable session,
                                              NSError * _Nullable error) {
    // Send SMS verification code.
    [FIRPhoneAuthProvider.provider
      verifyPhoneNumber:phoneNumber
      UIDelegate:nil
      multiFactorSession:session
      completion:^(NSString * _Nullable verificationID, NSError * _Nullable error) {
        // verificationId will be needed for enrollment completion.

        // Ask user for the verification code.
        // ...

        // Then:
        FIRPhoneAuthCredential *credential =
            [FIRPhoneAuthProvider.provider credentialWithVerificationID:verificationID
                                                        verificationCode:kPhoneSecondFactorVerificationCode];
        FIRMultiFactorAssertion *assertion =
            [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];

        // Complete enrollment. This will update the underlying tokens
        // and trigger ID token change listener.
        [user.multiFactor enrollWithAssertion:assertion
                                  displayName:displayName
                                    completion:^(NSError * _Nullable error) {
            // ...
        }];
    }];
}];

恭喜!您已成功为用户注册第二个身份验证因素。

使用第二因素登录用户

要使用双因素短信验证登录用户:

  1. 使用他们的第一个因素登录用户,然后捕获指示需要多重身份验证的错误。此错误包含一个解析器、有关注册的第二个因素的提示,以及一个证明用户已成功通过第一个因素进行身份验证的基础会话。

    例如,如果用户的第一个因素是电子邮件和密码:

    迅速

    Auth.auth().signIn(
      withEmail: email,
      password: password
    ) { (result, error) in
      let authError = error as NSError
      if authError?.code == AuthErrorCode.secondFactorRequired.rawValue {
        // The user is a multi-factor user. Second factor challenge is required.
        let resolver =
          authError!.userInfo[AuthErrorUserInfoMultiFactorResolverKey] as! MultiFactorResolver
        // ...
      } else {
        // Handle other errors such as wrong password.
      }
    }
    

    目标-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.
        }
    }];
    

    如果用户的第一个因素是联合提供者,例如 OAuth,则在调用getCredentialWith()后捕获错误。

  2. 如果用户注册了多个次要因素,请询问他们使用哪一个。您可以使用 resolver.hints[selectedIndex].phoneNumber 获取屏蔽电话号码,使用resolver.hints[selectedIndex].phoneNumber resolver.hints[selectedIndex].displayName显示名称。

    迅速

    // Ask user which second factor to use. Then:
    if resolver.hints[selectedIndex].factorID == PhoneMultiFactorID {
      // User selected a phone second factor.
      // ...
    } else {
      // Unsupported second factor.
      // Note that only phone second factors are currently supported.
    }
    

    目标-C

    FIRMultiFactorResolver *resolver =
        (FIRMultiFactorResolver *) error.userInfo[FIRAuthErrorUserInfoMultiFactorResolverKey];
    
    // Ask user which second factor to use. Then:
    FIRPhoneMultiFactorInfo *hint = (FIRPhoneMultiFactorInfo *) resolver.hints[selectedIndex];
    if (hint.factorID == FIRPhoneMultiFactorID) {
      // User selected a phone second factor.
      // ...
    } else {
      // Unsupported second factor.
      // Note that only phone second factors are currently supported.
    }
    
  3. 向用户手机发送验证信息:

    迅速

    // Send SMS verification code.
    let hint = resolver.hints[selectedIndex] as! PhoneMultiFactorInfo
    PhoneAuthProvider.provider().verifyPhoneNumber(
      with: hint,
      uiDelegate: nil,
      multiFactorSession: resolver.session
    ) { (verificationId, error) in
      // verificationId will be needed for sign-in completion.
    }
    

    目标-C

    // Send SMS verification code
    [FIRPhoneAuthProvider.provider
      verifyPhoneNumberWithMultiFactorInfo:hint
      UIDelegate:nil
      multiFactorSession:resolver.session
      completion:^(NSString * _Nullable verificationID, NSError * _Nullable error) {
        if (error != nil) {
            // Failed to verify phone number.
        }
    }];
    
  4. 发送短信代码后,请用户验证代码并使用它来构建PhoneAuthCredential

    迅速

    // Ask user for the verification code. Then:
    let credential = PhoneAuthProvider.provider().credential(
      withVerificationID: verificationId!,
      verificationCode: verificationCodeFromUser)
    

    目标-C

    // Ask user for the SMS verification code. Then:
    FIRPhoneAuthCredential *credential =
        [FIRPhoneAuthProvider.provider
          credentialWithVerificationID:verificationID
                      verificationCode:verificationCodeFromUser];
    
  5. 使用凭证初始化断言对象:

    迅速

    let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
    

    目标-C

    FIRMultiFactorAssertion *assertion =
        [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];
    
  6. 解决登录问题。然后,您可以访问原始登录结果,其中包括标准的提供商特定数据和身份验证凭据:

    迅速

    // Complete sign-in. This will also trigger the Auth state listeners.
    resolver.resolveSignIn(with: assertion) { (authResult, error) in
      // authResult will also contain the user, additionalUserInfo, optional
      // credential (null for email/password) associated with the first factor sign-in.
    
      // For example, if the user signed in with Google as a first factor,
      // authResult.additionalUserInfo will contain data related to Google provider that
      // the user signed in with.
    
      // user.credential contains the Google OAuth credential.
      // user.credential.accessToken contains the Google OAuth access token.
      // user.credential.idToken contains the Google OAuth ID token.
    }
    

    目标-C

    // Complete sign-in.
    [resolver resolveSignInWithAssertion:assertion
                              completion:^(FIRAuthDataResult * _Nullable authResult,
                                            NSError * _Nullable error) {
        if (error != nil) {
            // User successfully signed in with the second factor phone number.
        }
    }];
    

下面的代码显示了登录多因素用户的完整示例:

迅速

Auth.auth().signIn(
  withEmail: email,
  password: password
) { (result, error) in
  let authError = error as NSError?
  if authError?.code == AuthErrorCode.secondFactorRequired.rawValue {
    let resolver =
      authError!.userInfo[AuthErrorUserInfoMultiFactorResolverKey] as! MultiFactorResolver

    // Ask user which second factor to use.
    // ...

    // Then:
    let hint = resolver.hints[selectedIndex] as! PhoneMultiFactorInfo

    // Send SMS verification code
    PhoneAuthProvider.provider().verifyPhoneNumber(
      with: hint,
      uiDelegate: nil,
      multiFactorSession: resolver.session
    ) { (verificationId, error) in
      if error != nil {
        // Failed to verify phone number.
      }
      // Ask user for the SMS verification code.
      // ...

      // Then:
      let credential = PhoneAuthProvider.provider().credential(
        withVerificationID: verificationId!,
        verificationCode: verificationCodeFromUser)
      let assertion = PhoneMultiFactorGenerator.assertion(with: credential)

      // Complete sign-in.
      resolver.resolveSignIn(with: assertion) { (authResult, error) in
        if error != nil {
          // User successfully signed in with the second factor phone number.
        }
      }
    }
  }
}

目标-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 {
        FIRMultiFactorResolver *resolver =
            (FIRMultiFactorResolver *) error.userInfo[FIRAuthErrorUserInfoMultiFactorResolverKey];

        // Ask user which second factor to use.
        // ...

        // Then:
        FIRPhoneMultiFactorInfo *hint = (FIRPhoneMultiFactorInfo *) resolver.hints[selectedIndex];

        // Send SMS verification code
        [FIRPhoneAuthProvider.provider
          verifyPhoneNumberWithMultiFactorInfo:hint
                                    UIDelegate:nil
                            multiFactorSession:resolver.session
                                    completion:^(NSString * _Nullable verificationID,
                                                NSError * _Nullable error) {
            if (error != nil) {
                // Failed to verify phone number.
            }

            // Ask user for the SMS verification code.
            // ...

            // Then:
            FIRPhoneAuthCredential *credential =
                [FIRPhoneAuthProvider.provider
                  credentialWithVerificationID:verificationID
                              verificationCode:kPhoneSecondFactorVerificationCode];
            FIRMultiFactorAssertion *assertion =
                [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];

            // Complete sign-in.
            [resolver resolveSignInWithAssertion:assertion
                                      completion:^(FIRAuthDataResult * _Nullable authResult,
                                                    NSError * _Nullable error) {
                if (error != nil) {
                    // User successfully signed in with the second factor phone number.
                }
            }];
        }];
    }
}];

恭喜!您已使用多重身份验证成功登录用户。

下一步是什么