短信多重身份验证

如果您已升级到 Firebase Authentication with Identity Platform,则可以向 iOS 应用添加短信多重身份验证。

多重身份验证可提高应用的安全性。虽然攻击者通常会窃取到密码和社交账号,但截获短信却比较困难。

准备工作

  1. 请至少启用一个支持多重身份验证的提供方。每个提供方都支持 MFA,电话身份验证、匿名身份验证和 Apple Game Center 除外。
  2. 确保您的应用会验证用户的电子邮件地址。MFA 要求邮箱验证。这可防止恶意操作者使用别人的电子邮件地址注册服务,然后通过添加第二重身份验证阻止真正的电子邮件地址所有者注册。

启用多重身份验证

  1. 打开 Firebase 控制台的 Authentication > 登录方法页面。
  2. 在高级部分中,启用短信多重身份验证。

您还应输入要用于测试应用的电话号码。虽然并非必需,但我们强烈建议您注册测试电话号码,以免在开发过程中受到限制。 3. 如果您尚未向应用网域授权,请在 Firebase 控制台的 Authentication > 设置页面上将其添加到允许列表中。

验证应用

Firebase 需要验证短信请求是否来自您的应用。您可以通过以下两种方法实现此目的:

  • 静默 APNs 通知:用户首次登录时,Firebase 可以向用户的设备发送静默推送通知。如果应用收到通知,则可以继续进行身份验证。请注意,从 iOS 8.0 开始,您无需要求用户允许推送通知即可使用此方法。
  • reCAPTCHA 验证:如果您无法发送静默通知(例如,由于用户停用了后台刷新,或者您在 iOS 模拟器中测试应用),则可以使用 reCAPTCHA。在许多情况下,reCAPTCHA 会自动完成,无需与用户互动。

使用静默通知

如需启用 APNs 通知以用于 Firebase,请执行以下操作:

  1. 在 Xcode 中为您的项目启用推送通知。
  2. 使用 Firebase 控制台上传您的 APNs 身份验证密钥(您的更改将自动应用到 Google Cloud Firebase)。如果您还没有 APNs 身份验证密钥,请参阅配置 FCM APNs,了解如何获取密钥。

  3. 打开 Firebase 控制台。

  4. 前往项目设置。
  5. 选择 Cloud Messaging 标签页。
  6. 在 iOS 应用配置部分的 APNs 身份验证密钥下,点击上传以上传您的开发身份验证密钥、生产身份验证密钥,或两者。二者至少需要选择其一。
  7. 选择密钥。
  8. 添加该密钥的 ID。您可以在 Apple Developer Member Center 中的 Certificates, Identifiers & Profiles(证书、标识符和个人资料)下找到密钥 ID。
  9. 点击上传。

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

使用 reCAPTCHA 验证

如需让客户端 SDK 能够使用 reCAPTCHA,请执行以下操作:

  1. 在 Xcode 中打开您的项目配置。
  2. 在左侧树状视图中,双击项目名称。
  3. 从目标部分中选择您的应用。
  4. 选择信息标签页。
  5. 展开网址类型部分。
  6. 点击 + 按钮。
  7. 在网址方案字段中输入您的倒序客户端 ID。您可以在 GoogleService-Info.plist 配置文件中找到列为 REVERSED_CLIENT_ID 的此值。

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

自定义架构

(可选)您可以自定义应用在显示 reCAPTCHA 时呈现 SFSafariViewController 或 UIWebView 的方式。为此,请创建符合 FIRAuthUIDelegate 协议的自定义类,并将其传递给 verifyPhoneNumber:UIDelegate:completion:。

选择注册模式

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

  • 在注册过程中注册用户的第二重身份验证。如果应用要求所有用户进行多重身份验证,请使用此方法。请注意,账号必须具有经过验证的电子邮件地址才能注册第二重身份验证,您的注册流程必须考虑到这一点。
  • 提供可在注册期间跳过第二重身份验证注册的选项。如果应用鼓励但不要求进行多重身份验证,可以使用此方法。
  • 提供从用户的账号或个人资料管理页面(而不是注册界面)添加第二重身份验证的功能。这样可以使注册过程更顺畅,同时仍可为注重安全的用户提供多重身份验证。
  • 如果用户希望访问安全性要求更高的功能,再要求添加第二重身份验证。

注册第二重身份验证

如需为用户注册新的第二重身份验证,请执行以下操作:

  1. 重新验证用户身份。
  2. 让用户输入自己的电话号码。
  3. 为用户获取多重身份验证会话:

### Swift

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

### Objective-C

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

### Swift

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

### Objective-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. }];

(可选)最佳实践是事先告知用户他们会收到短信,并需按标准费率支付短信费用。

verifyPhoneNumber() 方法会使用静默推送通知在后台启动应用验证流程。如果静默推送通知不可用,系统会改为进行 reCAPTCHA 验证。 5. 短信验证码发出后,要求用户验证该验证码。然后,使用其响应来构建 PhoneAuthCredential:

### Swift

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

### Objective-C

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

### Swift

let assertion = PhoneMultiFactorGenerator.assertion(with: credential)

### Objective-C

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

### Swift

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

### Objective-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) { // ... }];

以下代码展示了注册第二重身份验证的完整示例:

Swift

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

Objective-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. 让用户通过第一重身份验证登录,然后捕获表明需要进行多重身份验证的错误。此错误包含一个解析器、有关已注册的第二重身份验证的提示以及一个证明用户已成功通过第一重身份验证的底层会话。

例如,如果用户的第一重身份验证是电子邮件地址和密码:

### Swift

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

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

如果用户的第一重身份验证是联合提供方(例如 OAuth),请在调用 getCredentialWith() 后捕获错误。 2. 如果用户注册了多个第二重身份验证,请询问用户要使用哪一个。您可以通过 resolver.hints[selectedIndex].phoneNumber 获取被部分遮盖的电话号码,使用 resolver.hints[selectedIndex].displayName 获取显示名。

### Swift

// Ask user which second factor to use. Then: if resolver.hints[selectedIndex].factorID == PhoneMultiFactorID { // User selected a phone second factor. // ... } else if resolver.hints[selectedIndex].factorID == TotpMultiFactorID { // User selected a TOTP second factor. // ... } else { // Unsupported second factor. }

### Objective-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 if (hint.factorID == FIRTOTPMultiFactorID) { // User selected a TOTP second factor. // ... } else { // Unsupported second factor. } ``` 3. 向用户的电话发送验证消息:

### Swift

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

### Objective-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:

### Swift

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

### Objective-C

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

### Swift

let assertion = PhoneMultiFactorGenerator.assertion(with: credential)

### Objective-C

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

### Swift

``` // 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.

} ```

### Objective-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. } }];

以下代码展示了让多重身份验证用户登录的完整示例:

Swift

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