Google 致力于为黑人社区推动种族平等。查看具体举措

使用 Apple 进行身份验证

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

通过使用 Firebase SDK 执行端到端 OAuth 2.0 登录流程,您可以让您的用户使用他们的 Apple ID 通过 Firebase 进行身份验证。

在你开始之前

要使用 Apple 登录用户,首先在 Apple 的开发者网站上配置使用 Apple 登录,然后启用 Apple 作为您的 Firebase 项目的登录提供商。

加入苹果开发者计划

Sign In with Apple 只能由Apple Developer Program的成员配置。

配置使用 Apple 登录

  1. 在 Apple 开发者网站的证书、标识符和配置文件页面上为您的应用程序启用 Sign In with Apple。
  2. 如果您使用 Firebase 身份验证的任何向用户发送电子邮件的功能,包括电子邮件链接登录、电子邮件地址验证、帐户更改撤销等,请配置 Apple 私人电子邮件中继服务并注册noreply@ YOUR_FIREBASE_PROJECT_ID .firebaseapp.com (或您自定义的电子邮件模板域),以便 Apple 可以将 Firebase 身份验证发送的电子邮件转发到匿名的 Apple 电子邮件地址。

启用 Apple 作为登录提供商

  1. 将 Firebase 添加到您的 Apple 项目。当您在 Firebase 控制台中设置您的应用程序时,请务必注册您的应用程序的 bundle ID。
  2. Firebase 控制台中,打开Auth部分。在登录方法选项卡上,启用Apple提供商。如果您仅在应用程序中使用 Sign In with Apple,您可以将服务 ID、Apple 团队 ID、私钥和密钥 ID 字段留空。

符合 Apple 匿名数据要求

Sign In with Apple 为用户提供了在登录时匿名化他们的数据的选项,包括他们的电子邮件地址。选择此选项的用户拥有域为privaterelay.appleid.com的电子邮件地址。当您在您的应用程序中使用 Sign In with Apple 时,您必须遵守 Apple 关于这些匿名 Apple ID 的任何适用的开发者政策或条款。

这包括在将任何直接识别个人信息与匿名 Apple ID 相关联之前获得任何必要的用户同意。使用 Firebase 身份验证时,这可能包括以下操作:

  • 将电子邮件地址链接到匿名的 Apple ID,反之亦然。
  • 将电话号码链接到匿名的 Apple ID,反之亦然
  • 将非匿名社交凭证(Facebook、Google 等)链接到匿名 Apple ID,反之亦然。

上面的列表并不详尽。请参阅您开发者帐户的“会员资格”部分中的 Apple 开发者计划许可协议,以确保您的应用程序符合 Apple 的要求。

使用 Apple 登录并使用 Firebase 进行身份验证

要使用 Apple 帐户进行身份验证,首先使用 Apple 的AuthenticationServices框架让用户登录到他们的 Apple 帐户,然后使用来自 Apple 响应的 ID 令牌创建一个 Firebase AuthCredential对象:

  1. 对于每个登录请求,生成一个随机字符串(一个“nonce”),您将使用它来确保您获得的 ID 令牌是专门为响应您的应用程序的身份验证请求而授予的。此步骤对于防止重放攻击很重要。

    您可以使用SecRandomCopyBytes(_:_:_)生成加密安全随机数,如以下示例所示:

    迅速

    // Adapted from https://auth0.com/docs/api-auth/tutorials/nonce#generate-a-cryptographically-random-nonce
    private func randomNonceString(length: Int = 32) -> String {
      precondition(length > 0)
      let charset: [Character] =
        Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._")
      var result = ""
      var remainingLength = length
    
      while remainingLength > 0 {
        let randoms: [UInt8] = (0 ..< 16).map { _ in
          var random: UInt8 = 0
          let errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &random)
          if errorCode != errSecSuccess {
            fatalError(
              "Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)"
            )
          }
          return random
        }
    
        randoms.forEach { random in
          if remainingLength == 0 {
            return
          }
    
          if random < charset.count {
            result.append(charset[Int(random)])
            remainingLength -= 1
          }
        }
      }
    
      return result
    }
    
        

    目标-C

    // Adapted from https://auth0.com/docs/api-auth/tutorials/nonce#generate-a-cryptographically-random-nonce
    - (NSString *)randomNonce:(NSInteger)length {
      NSAssert(length > 0, @"Expected nonce to have positive length");
      NSString *characterSet = @"0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._";
      NSMutableString *result = [NSMutableString string];
      NSInteger remainingLength = length;
    
      while (remainingLength > 0) {
        NSMutableArray *randoms = [NSMutableArray arrayWithCapacity:16];
        for (NSInteger i = 0; i < 16; i++) {
          uint8_t random = 0;
          int errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &random);
          NSAssert(errorCode == errSecSuccess, @"Unable to generate nonce: OSStatus %i", errorCode);
    
          [randoms addObject:@(random)];
        }
    
        for (NSNumber *random in randoms) {
          if (remainingLength == 0) {
            break;
          }
    
          if (random.unsignedIntValue < characterSet.length) {
            unichar character = [characterSet characterAtIndex:random.unsignedIntValue];
            [result appendFormat:@"%C", character];
            remainingLength--;
          }
        }
      }
    
      return [result copy];
    }
        

    您将随登录请求发送 nonce 的 SHA256 哈希值,Apple 将在响应中原封不动地传递该哈希值。 Firebase 通过散列原始随机数并将其与 Apple 传递的值进行比较来验证响应。

    迅速

    @available(iOS 13, *)
    private func sha256(_ input: String) -> String {
      let inputData = Data(input.utf8)
      let hashedData = SHA256.hash(data: inputData)
      let hashString = hashedData.compactMap {
        String(format: "%02x", $0)
      }.joined()
    
      return hashString
    }
    
        

    目标-C

    - (NSString *)stringBySha256HashingString:(NSString *)input {
      const char *string = [input UTF8String];
      unsigned char result[CC_SHA256_DIGEST_LENGTH];
      CC_SHA256(string, (CC_LONG)strlen(string), result);
    
      NSMutableString *hashed = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
      for (NSInteger i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
        [hashed appendFormat:@"%02x", result[i]];
      }
      return hashed;
    }
        
  2. 启动 Apple 的登录流程,在您的请求中包括随机数的 SHA256 哈希和将处理 Apple 响应的委托类(请参阅下一步):

    迅速

    import CryptoKit
    
    // Unhashed nonce.
    fileprivate var currentNonce: String?
    
    @available(iOS 13, *)
    func startSignInWithAppleFlow() {
      let nonce = randomNonceString()
      currentNonce = nonce
      let appleIDProvider = ASAuthorizationAppleIDProvider()
      let request = appleIDProvider.createRequest()
      request.requestedScopes = [.fullName, .email]
      request.nonce = sha256(nonce)
    
      let authorizationController = ASAuthorizationController(authorizationRequests: [request])
      authorizationController.delegate = self
      authorizationController.presentationContextProvider = self
      authorizationController.performRequests()
    }
    

    目标-C

    @import CommonCrypto;
    
    - (void)startSignInWithAppleFlow {
      NSString *nonce = [self randomNonce:32];
      self.currentNonce = nonce;
      ASAuthorizationAppleIDProvider *appleIDProvider = [[ASAuthorizationAppleIDProvider alloc] init];
      ASAuthorizationAppleIDRequest *request = [appleIDProvider createRequest];
      request.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];
      request.nonce = [self stringBySha256HashingString:nonce];
    
      ASAuthorizationController *authorizationController =
          [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]];
      authorizationController.delegate = self;
      authorizationController.presentationContextProvider = self;
      [authorizationController performRequests];
    }
    
  3. ASAuthorizationControllerDelegate的实现中处理 Apple 的响应。如果登录成功,请使用 Apple 响应中的 ID 令牌和未经哈希处理的随机数来通过 Firebase 进行身份验证:

    迅速

    @available(iOS 13.0, *)
    extension MainViewController: ASAuthorizationControllerDelegate {
    
      func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
          guard let nonce = currentNonce else {
            fatalError("Invalid state: A login callback was received, but no login request was sent.")
          }
          guard let appleIDToken = appleIDCredential.identityToken else {
            print("Unable to fetch identity token")
            return
          }
          guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
            print("Unable to serialize token string from data: \(appleIDToken.debugDescription)")
            return
          }
          // Initialize a Firebase credential.
          let credential = OAuthProvider.credential(withProviderID: "apple.com",
                                                    IDToken: idTokenString,
                                                    rawNonce: nonce)
          // Sign in with Firebase.
          Auth.auth().signIn(with: credential) { (authResult, error) in
            if error {
              // Error. If error.code == .MissingOrInvalidNonce, make sure
              // you're sending the SHA256-hashed nonce as a hex string with
              // your request to Apple.
              print(error.localizedDescription)
              return
            }
            // User is signed in to Firebase with Apple.
            // ...
          }
        }
      }
    
      func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
        // Handle error.
        print("Sign in with Apple errored: \(error)")
      }
    
    }
    

    目标-C

    - (void)authorizationController:(ASAuthorizationController *)controller
       didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)) {
      if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
        ASAuthorizationAppleIDCredential *appleIDCredential = authorization.credential;
        NSString *rawNonce = self.currentNonce;
        NSAssert(rawNonce != nil, @"Invalid state: A login callback was received, but no login request was sent.");
    
        if (appleIDCredential.identityToken == nil) {
          NSLog(@"Unable to fetch identity token.");
          return;
        }
    
        NSString *idToken = [[NSString alloc] initWithData:appleIDCredential.identityToken
                                                  encoding:NSUTF8StringEncoding];
        if (idToken == nil) {
          NSLog(@"Unable to serialize id token from data: %@", appleIDCredential.identityToken);
        }
    
        // Initialize a Firebase credential.
        FIROAuthCredential *credential = [FIROAuthProvider credentialWithProviderID:@"apple.com"
                                                                            IDToken:idToken
                                                                           rawNonce:rawNonce];
    
        // Sign in with Firebase.
        [[FIRAuth auth] signInWithCredential:credential
                                  completion:^(FIRAuthDataResult * _Nullable authResult,
                                               NSError * _Nullable error) {
          if (error != nil) {
            // Error. If error.code == FIRAuthErrorCodeMissingOrInvalidNonce,
            // make sure you're sending the SHA256-hashed nonce as a hex string
            // with your request to Apple.
            return;
          }
          // Sign-in succeeded!
        }];
      }
    }
    
    - (void)authorizationController:(ASAuthorizationController *)controller
               didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0)) {
      NSLog(@"Sign in with Apple errored: %@", error);
    }
    

与 Firebase Auth 支持的其他提供商不同,Apple 不提供照片 URL。

此外,当用户选择不与应用程序共享他们的电子邮件时,Apple 会为该用户提供一个唯一的电子邮件地址(格式xyz@privaterelay.appleid.com ),并将其与您的应用程序共享。如果您配置了私人电子邮件中继服务,Apple 会将发送到匿名地址的电子邮件转发到用户的真实电子邮件地址。

Apple 仅在用户首次登录时与应用共享用户信息,例如显示名称。通常,Firebase 会在用户首次登录 Apple 时存储显示名称,您可以通过Auth.auth().currentUser.displayName 。但是,如果您之前使用 Apple 在不使用 Firebase 的情况下让用户登录应用程序,Apple 将不会向 Firebase 提供用户的显示名称。

重新验证和帐户链接

相同的模式可以与reauthenticateWithCredential()一起使用,您可以使用它来为需要最近登录的敏感操作检索新的凭证:

迅速

// Initialize a fresh Apple credential with Firebase.
let credential = OAuthProvider.credential(
  withProviderID: "apple.com",
  IDToken: appleIdToken,
  rawNonce: rawNonce
)
// Reauthenticate current Apple user with fresh Apple credential.
Auth.auth().currentUser.reauthenticate(with: credential) { (authResult, error) in
  guard error != nil else { return }
  // Apple user successfully re-authenticated.
  // ...
}

目标-C

FIRAuthCredential *credential = [FIROAuthProvider credentialWithProviderID:@"apple.com",
                                                                   IDToken:appleIdToken,
                                                                  rawNonce:rawNonce];
[[FIRAuth auth].currentUser
    reauthenticateWithCredential:credential
                      completion:^(FIRAuthDataResult * _Nullable authResult,
                                   NSError * _Nullable error) {
  if (error) {
    // Handle error.
  }
  // Apple user successfully re-authenticated.
  // ...
}];

而且,您可以使用linkWithCredential()将不同的身份提供者链接到现有帐户。

请注意,Apple 要求您在将用户的 Apple 帐户链接到其他数据之前获得用户的明确同意。

使用 Apple 登录将不允许您重复使用授权凭据来链接到现有帐户。如果您想将 Sign in with Apple 凭据链接到另一个帐户,您必须首先尝试使用旧的 Sign in with Apple 凭据链接帐户,然后检查返回的错误以找到新的凭据。新凭证将位于错误的userInfo字典中,可以通过AuthErrorUserInfoUpdatedCredentialKey键访问。

例如,要将 Facebook 帐户链接到当前的 Firebase 帐户,请使用您通过将用户登录到 Facebook 获得的访问令牌:

迅速

// Initialize a Facebook credential with Firebase.
let credential = FacebookAuthProvider.credential(
  withAccessToken: AccessToken.current!.tokenString
)
// Assuming the current user is an Apple user linking a Facebook provider.
Auth.auth().currentUser.link(with: credential) { (authResult, error) in
  // Facebook credential is linked to the current Apple user.
  // The user can now sign in with Facebook or Apple to the same Firebase
  // account.
  // ...
}

目标-C

// Initialize a Facebook credential with Firebase.
FacebookAuthCredential *credential = [FIRFacebookAuthProvider credentialWithAccessToken:accessToken];
// Assuming the current user is an Apple user linking a Facebook provider.
[FIRAuth.auth linkWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) {
  // Facebook credential is linked to the current Apple user.
  // The user can now sign in with Facebook or Apple to the same Firebase
  // account.
  // ...
}];

下一步

用户首次登录后,将创建一个新的用户帐户并将其链接到用户登录所用的凭据,即用户名和密码、电话号码或身份验证提供商信息。这个新帐户存储为您的 Firebase 项目的一部分,可用于在项目中的每个应用程序中识别用户,无论用户如何登录。

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

  • 在您的 Firebase Realtime Database 和 Cloud Storage Security Rules中,您可以从auth变量中获取登录用户的唯一用户 ID,并使用它来控制用户可以访问哪些数据。

您可以允许用户使用多个身份验证提供程序登录您的应用程序,方法是将身份验证提供程序凭据链接到现有用户帐户。

要注销用户,请调用signOut:

迅速

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

目标-C

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

您可能还想为所有身份验证错误添加错误处理代码。请参阅处理错误