在 iOS 上使用 Apple 進行身份驗證

通過使用 Firebase SDK 執行端到端 OAuth 2.0 登錄流程,您可以讓您的用戶使用他們的 Apple ID 向 Firebase 進行身份驗證。

在你開始之前

要使用 Apple 登錄用戶,請先在 Apple 的開發者網站上配置使用 Apple 登錄,然後啟用 Apple 作為 Firebase 項目的登錄提供程序。

加入 Apple 開發者計劃

登錄與蘋果只能由成員進行配置的蘋果開發者計劃

配置使用 Apple 登錄

  1. 有關您的應用程序啟用登錄與蘋果證書,標識和簡介蘋果的開發者網站的頁面。
  2. 如果你使用任何的火力地堡認證的功能,發送電子郵件給用戶,包括電子郵件鏈接登錄,電子郵件地址驗證,賬戶撤銷的變化,和其他人,配置了蘋果私人電子郵件中繼服務和註冊noreply@ YOUR_FIREBASE_PROJECT_ID .firebaseapp.com (或您自定義的電子郵件模板域),以便 Apple 可以將 Firebase 身份驗證發送的電子郵件中繼到匿名的 Apple 電子郵件地址。

啟用 Apple 作為登錄提供商

  1. 添加火力地堡到iOS項目。在 Firebase 控制台中設置應用程序時,請務必註冊應用程序的捆綁包 ID。
  2. 火力地堡控制台,打開驗證部分。在登錄方法選項卡,使蘋果的供應商。如果您僅在 iOS 應用程序中使用 Apple 登錄,您可以將服務 ID、Apple 團隊 ID、私鑰和密鑰 ID 字段留空。

遵守 Apple 匿名數據要求

誰選擇了這個選項拍在與蘋果公司為用戶提供了匿名的數據,包括他們的電子郵件地址,在登錄時的選項,用戶必須與該域的電子郵件地址privaterelay.appleid.com 。當您在您的應用程序中使用 Apple 登錄時,您必須遵守任何適用的開發者政策或 Apple 有關這些匿名 Apple ID 的條款。

這包括在您將任何直接識別個人信息與匿名 Apple ID 關聯之前獲得任何必要的用戶同意。使用 Firebase 身份驗證時,這可能包括以下操作:

  • 將電子郵件地址鏈接到匿名 Apple ID,反之亦然。
  • 將電話號碼與匿名 Apple ID 關聯,反之亦然
  • 將非匿名社交憑據(Facebook、Google 等)鏈接到匿名 Apple ID,反之亦然。

上面的列表並不詳盡。請參閱您的開發者帳戶成員資格部分中的 Apple 開發者計劃許可協議,以確保您的應用符合 Apple 的要求。

使用 Apple 登錄並使用 Firebase 進行身份驗證

為了與蘋果帳戶進行身份驗證,先登錄用戶在他們的蘋果使用蘋果的帳戶AuthenticationServices框架,然後用從蘋果公司的響應令牌的ID來創建一個火力地堡AuthCredential對象:

  1. 對於每個登錄請求,生成一個隨機字符串——一個“nonce”——你將使用它來確保你獲得的 ID 令牌是專門為響應你的應用程序的身份驗證請求而授予的。這一步對於防止重放攻擊很重要。

    您可以生成與iOS上加密的安全隨機數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: Array<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

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

    您將隨登錄請求發送隨機數的 SHA256 哈希值,Apple 將在響應中原封不動地傳遞該哈希值。 Firebase 通過散列原始隨機數並將其與 Apple 傳遞的值進行比較來驗證響應。

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

    目標-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];
    }
    
    - (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;
    }
    
  3. 處理蘋果在你執行應對ASAuthorizationControllerDelegate 。如果登錄成功,請使用 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。

此外,當用戶選擇不與應用程序,蘋果公司規定一個唯一的電子郵件地址的用戶(形式分享他們的電子郵件xyz@privaterelay.appleid.com ,其股您的應用程序)。如果您配置了私人電子郵件中繼服務,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 登錄將不允許您重複使用身份驗證憑據來鏈接到現有帳戶。如果您想將使用 Apple 憑據登錄的帳戶鏈接到另一個帳戶,您必須首先嘗試使用舊的使用 Apple 憑據登錄的帳戶進行鏈接,然後檢查返回的錯誤以查找新憑據。新證書將位於錯誤的userInfo字典,可通過訪問FIRAuthErrorUserInfoUpdatedCredentialKey關鍵。

例如,要將 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 項目的一部分存儲,可用於識別項目中每個應用中的用戶,無論用戶如何登錄。

  • 在您的應用程序,你可以從用戶的基本資料信息FIRUser對象。請參閱管理用戶

  • 在你的火力地堡實時數據庫和雲存儲安全規則,你可以得到簽署的,從用戶的唯一的用戶ID auth的變量,並用它來控制哪些數據的用戶可以訪問。

您可以允許用戶通過使用多個身份驗證提供登錄到您的應用程序連接身份驗證提供憑據到現有的用戶帳戶。

要註銷用戶,請撥打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;
}

您可能還想為所有身份驗證錯誤添加錯誤處理代碼。請參見處理錯誤