在 iOS 上使用电话号码进行 Firebase 身份验证

借助 Firebase 身份验证,您可以通过向用户电话发送短信的方式协助用户登录。用户使用短信中包含的一次性验证码登录。

向应用中添加电话号码登录方式的最简单方法是使用 FirebaseUI,它包括一个普适性登录微件,可为电话号码登录方式、基于密码的登录方式和联合登录方式实现登录流程。本文档介绍了如何使用 Firebase SDK 实现电话号码登录流程。

开始之前

  1. 将 Firebase 添加到您的 iOS 项目
  2. 在您的 Podfile 中添加以下 pod:
    pod 'Firebase/Auth'
    
  3. 如果您尚未将自己的应用与您的 Firebase 项目关联,请在 Firebase 控制台中进行关联。

安全问题

与其他可用的方法相比,仅使用电话号码进行身份验证的方式虽然便捷,但安全性较低,因为同一电话号码很容易流转给不同用户使用。此外,在具有多份用户个人资料的设备上,任何一位可以收到短信的用户都能使用该设备的电话号码登录帐号。

如果您在应用中使用基于电话号码的登录,则应同时提供更安全的登录方法,并将使用电话号码登录的安全隐患告知用户。

为您的 Firebase 项目启用电话号码登录

要让用户能够通过短信登录,您必须先为 Firebase 项目启用电话号码登录功能,操作步骤如下:

  1. Firebase 控制台中,打开身份验证 (Authentication) 部分。
  2. 登录方法页面上,启用电话号码登录方法。

Firebase 的电话号码登录请求的配额非常充足,大多数应用都不会受到配额问题的影响。但是,如果您需要通过电话号码身份验证让数量非常多的用户登录,则可能需要升级您的定价方案。请参阅定价页面。

启用应用验证

要使用电话号码身份验证,Firebase 必须要能验证电话号码登录请求是否来自您的应用。Firebase 身份验证可通过以下两种方法完成此操作:

  • 静默 APNs 通知:用户首次通过电话号码在设备上登录时,Firebase 身份验证会通过一条静默推送通知向设备发送令牌。如果您的应用成功收到 Firebase 的通知,就可以继续进行电话号码登录。

    对于 iOS 8.0 及更高版本,静默通知不需要用户明确同意,因此,即使用户拒绝在应用中接收 APNs 通知也不受影响。如此一来,在实现 Firebase 电话号码身份验证时,应用不需要请求用户授予权限即可接收推送通知。

  • reCAPTCHA 验证:如果无法发送或接收静默推送通知(例如,如果用户为应用停用了后台刷新,或者当您在 iOS 模拟器上测试应用时),Firebase 身份验证会使用 reCAPTCHA 验证来完成电话登录流程。reCAPTCHA 质询通常可以在不需要用户解决任何问题的情况下完成。

如果正确配置了静默推送通知,只有极少数的用户会经历 reCAPTCHA 流程。尽管如此,您应该确保无论静默推送通知是否可用,电话号码登录功能都可正常工作。

开始接收静默通知

要启用 APNs 通知以用于 Firebase 身份验证,请执行以下操作:

  1. 在 Xcode 中为您的项目启用推送通知
  2. 将您的 APNs 身份验证密钥上传到 Firebase。如果您还没有 APNs 身份验证密钥,请参阅配置 FCM APNs

    1. 在 Firebase 控制台中,在您的项目内依次选择齿轮图标、项目设置以及云消息传递标签。

    2. iOS 应用配置下的 APNs 身份验证密钥中,点击上传按钮。

    3. 转到您保存密钥的位置,选择该密钥,然后点击打开。添加该密钥的密钥 ID(可在 Apple Developer Member CenterCertificates, Identifiers & Profiles 中找到),然后点击上传

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

设置 reCAPTCHA 验证

要让 Firebase SDK 能使用 reCAPTCHA 验证,请执行以下操作:

  1. 向您的 Xcode 项目添加自定义网址架构:
    1. 打开您的项目配置:在左侧的树状视图中双击项目名称。从 TARGETS 部分中选择您的应用,然后选择 Info 标签,并展开 URL Types 部分。
    2. 点击 + 按钮,并为您的倒序客户端 ID 添加一个网址架构。要找到该值,请打开 GoogleService-Info.plist 配置文件,然后查找 REVERSED_CLIENT_ID 键。复制该键的值,并将其粘贴到配置页面上的 URL Schemes 框中。将其他字段留空。

      完成上述操作后,您的配置应显示如下(但其中的值应替换为您的应用特有的值):

  2. 可选:如果要自定义应用在向用户显示 reCAPTCHA 时呈现 SFSafariViewControllerUIWebView 的方式,请创建一个符合 FIRAuthUIDelegate 协议的自定义类,并将其传递给 verifyPhoneNumber:UIDelegate:completion:

向用户的电话发送验证码

要启动电话号码登录,请向用户显示一个要求其提供电话号码的界面,然后调用 verifyPhoneNumber:UIDelegate:completion: 以请求 Firebase 通过短信向用户电话发送身份验证代码:

  1. 获取用户的电话号码。

    虽然各地相关的法律要求可能不尽相同,但为了让用户心中有数,最佳做法是将以下事宜告知用户:如果用户使用电话登录方式,则可能会收到一条验证短信,并需按标准费率支付短信费用。

  2. 调用 verifyPhoneNumber:UIDelegate:completion:,并向其传递用户的电话号码。

    Swift

    PhoneAuthProvider.provider().verifyPhoneNumber(phoneNumber, uiDelegate: nil) { (verificationID, error) in
      if let error = error {
        self.showMessagePrompt(error.localizedDescription)
        return
      }
      // Sign in using the verificationID and the code sent to the user
      // ...
    }

    Objective-C

    [[FIRPhoneAuthProvider provider] verifyPhoneNumber:userInput
                                            UIDelegate:nil
                                            completion:^(NSString * _Nullable verificationID, NSError * _Nullable error) {
      if (error) {
        [self showMessagePrompt:error.localizedDescription];
        return;
      }
      // Sign in using the verificationID and the code sent to the user
      // ...
    }];

    当您调用 verifyPhoneNumber:UIDelegate:completion: 时,Firebase 会向您的应用发送一条静默推送通知,或向用户发出 reCAPTCHA 质询。在您的应用收到通知或者用户完成 reCAPTCHA 质询后,Firebase 会向指定的电话号码发送一条包含身份验证代码的短信,并将验证 ID 传递给您的完成函数。您需要有验证码和验证 ID 才能让用户登录。

    您还可以通过身份验证实例中的 languageCode 属性指定身份验证语言,以对 Firebase 发送的短信进行本地化。

    Swift

     // Change language code to french.
     Auth.auth().languageCode = "fr";
    

    Objective-C

     // Change language code to french.
     [FIRAuth auth].languageCode = @"fr";
    
  3. 保存验证 ID,并在应用加载时将其还原。这样可确保一旦您的应用在用户完成登录流程之前被终止,您仍然拥有有效的验证 ID(例如,切换到短信应用时)。

    您可以用任何方式永久保留验证 ID。一个简单的方法是使用 NSUserDefaults 对象保存验证 ID:

    Swift

    UserDefaults.standard.set(verificationID, forKey: "authVerificationID")
    

    Objective-C

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setObject:verificationID forKey:@"authVerificationID"];
    

    然后,您可以还原保存的值:

    Swift

    let verificationID = UserDefaults.standard.string(forKey: "authVerificationID")
    

    Objective-C

    NSString *verificationID = [defaults stringForKey:@"authVerificationID"];
    

如果成功调用 verifyPhoneNumber:UIDelegate:completion:,那么您可以提示用户在收到验证码短信时输入验证码。

让用户使用验证码登录

用户将短信中的验证码提供给您的应用之后,通过从验证码和验证 ID 创建一个 FIRPhoneAuthCredential 对象并将此对象传递给 signInWithCredential:completion: 使用户登录。

  1. 从用户处获取验证码。
  2. 从验证码和验证 ID 创建 FIRPhoneAuthCredential 对象。

    Swift

    let credential = PhoneAuthProvider.provider().credential(
        withVerificationID: verificationID,
        verificationCode: verificationCode)

    Objective-C

    FIRAuthCredential *credential = [[FIRPhoneAuthProvider provider]
        credentialWithVerificationID:verificationID
                    verificationCode:userInput];
  3. 使用 FIRPhoneAuthCredential 对象使用户登录:

    Swift

    Auth.auth().signIn(with: credential) { (user, error) in
      if let error = error {
        // ...
        return
      }
      // User is signed in
      // ...
    }
    }

    Objective-C

    [[FIRAuth auth] signInWithCredential:credential
                              completion:^(FIRUser *user, NSError *error) {
      if (error) {
        // ...
        return;
      }
      // User successfully signed in. Get user data from the FIRUser object
      // ...
    }];

附录:使用电话号码登录而不调配

Firebase 身份验证使用方法调配 (method swizzling) 自动获取您的应用的 APNs 令牌,处理 Firebase 发送给您的应用的静默推送通知,并自动拦截在验证期间来自 reCAPTCHA 验证页面的自定义架构重定向。

如果您不想使用调配,可以通过向应用的 Info.plist 文件添加标记 FirebaseAppDelegateProxyEnabled 并将其设置为 NO 来停用调配。请注意,将此标记设置为 NO 也会为其他 Firebase 产品(包括 Firebase 云消息传递)停用调配。

如果停用调配,您必须显式传递 APNs 设备令牌、推送通知和自定义架构重定向网址给 Firebase 身份验证。

要获取 APNs 设备令牌,请实现 application:didRegisterForRemoteNotificationsWithDeviceToken: 方法,并在其中将设备令牌传递给 FIRAuthsetAPNSToken:type: 方法。

Swift

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
  // Pass device token to auth
  Auth.auth().setAPNSToken(deviceToken, type: AuthAPNSTokenTypeProd)

  // Further handling of the device token if needed by the app
  // ...
}

Objective-C

- (void)application:(UIApplication *)application
    didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
  // Pass device token to auth.
  [[FIRAuth auth] setAPNSToken:deviceToken type:FIRAuthAPNSTokenTypeProd];
  // Further handling of the device token if needed by the app.
}

要处理推送通知,请在 application:didReceiveRemoteNotification:fetchCompletionHandler: 方法中,通过调用 FIRAuthcanHandleNotification: 方法检查 Firebase 身份验证相关的通知。

Swift

func application(_ application: UIApplication,
    didReceiveRemoteNotification notification: [AnyHashable : Any],
    fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
  if Auth.auth().canHandleNotification(notification) {
    completionHandler(UIBackgroundFetchResultNoData)
    return
  }
  // This notification is not auth related, developer should handle it.
}

Objective-C

- (void)application:(UIApplication *)application
    didReceiveRemoteNotification:(NSDictionary *)notification
          fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
  // Pass notification to auth and check if they can handle it.
  if ([[FIRAuth auth] canHandleNotification:notification]) {
    completionHandler(UIBackgroundFetchResultNoData);
    return;
  }
  // This notification is not auth related, developer should handle it.
}

要处理自定义架构重定向网址,请为运行 iOS 8 及更早版本的设备实现 application:openURL:sourceApplication:annotation: 方法,为运行 iOS 9 及更高版本的设备实现 application:openURL:options: 方法,并在两个方法中传递网址给 FIRAuthcanHandleURL 方法。

Swift

// For iOS 9+
func application(_ application: UIApplication, open url: URL,
    options: [UIApplicationOpenURLOptionsKey : Any]) -> Bool {
  if Auth.auth().canHandle(url) {
    return true
  }
  // URL not auth related, developer should handle it.
}

// For iOS 8-
func application(_ application: UIApplication,
                 open url: URL,
                 sourceApplication: String?,
                 annotation: Any) -> Bool {
  if Auth.auth().canHandle(url) {
    Return true
  }
  // URL not auth related, developer should handle it.
}

Objective-C

// For iOS 9+
- (BOOL)application:(UIApplication *)app
            openURL:(NSURL *)url
            options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
  if ([[FIRAuth auth] canHandleURL:url]) {
    return YES;
  }
  // URL not auth related, developer should handle it.
}

// For iOS 8-
- (BOOL)application:(UIApplication *)application
            openURL:(NSURL *)url
  sourceApplication:(NSString *)sourceApplication
         annotation:(id)annotation {
  if ([[FIRAuth auth] canHandleURL:url]) {
    return YES;
  }
  // URL not auth related, developer should handle it.
}

后续步骤

在用户首次登录后,系统会创建一个新的用户帐号,并将其与该用户登录时使用的凭据(即用户名和密码、电话号码或者身份验证提供方信息)相关联。此新帐号存储在您的 Firebase 项目中,您项目中的每个应用都可以使用此帐号来识别用户,无论用户采用何种方式登录。

  • 在您的应用中,您可以从 FIRUser 对象获取用户的个人资料基本信息。请参阅管理用户

  • 在您的 Firebase 实时数据库和 Cloud Storage 安全规则中,您可以从 auth 变量中获取已登录用户的唯一用户 ID,然后用此 ID 来控制用户可以访问哪些数据。

您可以允许用户通过多种身份验证提供方服务登录您的应用,只需将多个身份验证提供方凭据关联至现有用户帐号即可。

要让用户退出登录,可调用 signOut:

Swift

    let firebaseAuth = Auth.auth()
do {
  try firebaseAuth.signOut()
} catch let signOutError as NSError {
  print ("Error signing out: %@", signOutError)
}
  

Objective-C

    NSError *signOutError;
BOOL status = [[FIRAuth auth] signOut:&signOutError];
if (!status) {
  NSLog(@"Error signing out: %@", signOutError);
  return;
}

您可能还需要添加适用于所有身份验证错误的错误处理代码。请参阅处理错误

发送以下问题的反馈:

此网页
需要帮助?请访问我们的支持页面