使用 Apple 和 C++ 進行身份驗證

您可以使用 Firebase SDK 執行端對端 OAuth 2.0 登入流程,讓使用者使用其 Apple ID 透過 Firebase 進行身份驗證。

在你開始之前

若要使用 Apple 登入用戶,請先在 Apple 開發者網站上設定 Sign In with Apple,然後啟用 Apple 作為 Firebase 專案的登入提供者。

加入蘋果開發者計劃

「使用 Apple 登入」只能由Apple 開發者計畫的成員進行設定。

配置使用 Apple 登入

必須在您的 Firebase 專案中啟用並正確配置 Apple Sign In。 Android 和 Apple 平台的配置有所不同。請先依照Apple 平台和/或Android指南的「設定透過 Apple 登入」部分進行操作,然後再繼續。

啟用 Apple 作為登入供應商

  1. Firebase 控制台中,開啟「驗證」部分。在「登入方法」標籤上,啟用Apple提供者。
  2. 配置 Apple 登入提供者設定:
    1. 如果您僅在 Apple 平台上部署應用程序,則可以將服務 ID、Apple 團隊 ID、私鑰和金鑰 ID 欄位留空。
    2. 對於 Android 裝置的支援:
      1. 將 Firebase 新增到您的 Android 專案。在 Firebase 控制台中設定應用程式時,請務必註冊應用程式的 SHA-1 簽章。
      2. Firebase 控制台中,開啟「驗證」部分。在「登入方法」標籤上,啟用Apple提供者。指定您在上一部分中建立的服務 ID。此外,在 OAuth 程式碼流配置部分中,指定您的 Apple 團隊 ID 以及您在上一部分中建立的私鑰和金鑰 ID。

遵守 Apple 匿名資料要求

「使用 Apple 登入」為使用者提供了在登入時對其資料(包括電子郵件地址)進行匿名化的選項。選擇此選項的使用者的電子郵件地址的網域為privaterelay.appleid.com 。當您在應用程式中使用「透過 Apple 登入」時,您必須遵守 Apple 關於這些匿名 Apple ID 的任何適用的開發者政策或條款。

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

  • 將電子郵件地址連結到匿名 Apple ID,反之亦然。
  • 將電話號碼連結到匿名 Apple ID,反之亦然
  • 將非匿名社交憑證(Facebook、Google 等)連結到匿名 Apple ID,反之亦然。

上述列表並非詳盡無遺。請參閱開發者帳戶的「會員資格」部分中的 Apple 開發者計畫許可協議,以確保您的應用程式符合 Apple 的要求。

存取firebase::auth::Auth

Auth類別是所有 API 呼叫的網關。
  1. 新增Auth和App頭檔:
    #include "firebase/app.h"
    #include "firebase/auth.h"
    
  2. 在初始化程式碼中,建立一個firebase::App類別。
    #if defined(__ANDROID__)
      firebase::App* app =
          firebase::App::Create(firebase::AppOptions(), my_jni_env, my_activity);
    #else
      firebase::App* app = firebase::App::Create(firebase::AppOptions());
    #endif  // defined(__ANDROID__)
    
  3. 取得 firebase firebase::App firebase::auth::Auth類別。 AppAuth之間存在一對一的對應。
    firebase::auth::Auth* auth = firebase::auth::Auth::GetAuth(app);
    

使用 Firebase SDK 處理登入流程

登入 Apple 的過程因 Apple 和 Android 平台而異。

在蘋果平台上

透過從您的 C++ 程式碼呼叫的 Apple Sign In Objective-C SDK,使用 Firebase 對您的使用者進行身份驗證。

  1. 對於每個登入請求,產生一個隨機字串(「nonce」),您將使用它來確保您獲得的 ID 令牌是專門為回應您的應用程式的身份驗證請求而授予的。此步驟對於防止重播攻擊很重要。

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

    您將隨登入請求一起傳送隨機數的 SHA256 雜湊值,Apple 將在回應中原封不動地傳遞該雜湊值。 Firebase 透過對原始隨機數進行雜湊處理並將其與 Apple 傳遞的值進行比較來驗證回應。

  2. 啟動 Apple 的登入流程,在您的請求中包含隨機數的 SHA256 雜湊值以及將處理 Apple 回應的委託類別(請參閱下一步):

      - (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 的回應。如果登入成功,請使用 Apple 回應中的 ID 令牌以及未散列的隨機數字來透過 Firebase 進行身份驗證:

      - (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);
          }
        }
    
  4. 使用產生的令牌字串和原始隨機數建立 Firebase 憑證並登入 Firebase。

    firebase::auth::OAuthProvider::GetCredential(
            /*provider_id=*/"apple.com", token, nonce,
            /*access_token=*/nullptr);
    
    firebase::Future<firebase::auth::AuthResult> result =
        auth->SignInAndRetrieveDataWithCredential(credential);
    
  5. 相同的模式可與Reauthenticate一起使用,重新驗證可用於擷取需要最近登入的敏感操作的新憑證。

    firebase::Future<firebase::auth::AuthResult> result =
        user->Reauthenticate(credential);
    
  6. 相同的模式可用於將帳戶與 Apple Sign In 關聯。但是,當現有 Firebase 帳戶已連結到您嘗試連結的 Apple 帳戶時,您可能會遇到錯誤。發生這種情況時,未來將傳回kAuthErrorCredentialAlreadyInUse狀態,且AuthResult可能包含有效的credential 。此憑證可用於透過SignInAndRetrieveDataWithCredential登入 Apple 連結的帳戶,而無需產生另一個 Apple 登入令牌和隨機數。

    firebase::Future<firebase::auth::AuthResult> link_result =
        auth->current_user().LinkWithCredential(credential);
    
    // To keep example simple, wait on the current thread until call completes.
    while (link_result.status() == firebase::kFutureStatusPending) {
      Wait(100);
    }
    
    // Determine the result of the link attempt
    if (link_result.error() == firebase::auth::kAuthErrorNone) {
      // user linked correctly.
    } else if (link_result.error() ==
                   firebase::auth::kAuthErrorCredentialAlreadyInUse &&
               link_result.result()
                   ->additional_user_info.updated_credential.is_valid()) {
      // Sign In with the new credential
      firebase::Future<firebase::auth::AuthResult> result =
          auth->SignInAndRetrieveDataWithCredential(
              link_result.result()->additional_user_info.updated_credential);
    } else {
      // Another link error occurred.
    }
    

在安卓上

在 Android 上,使用 Firebase SDK 將基於 Web 的通用 OAuth 登入整合到您的應用程式中,以執行端對端登入流程,從而透過 Firebase 對您的使用者進行身份驗證。

若要使用 Firebase SDK 處理登入流程,請依照下列步驟操作:

  1. 建構一個FederatedOAuthProviderData實例,並配置適合 Apple 的提供者 ID。

    firebase::auth::FederatedOAuthProviderData provider_data("apple.com");
    
  2. 選用:指定您想要從身分驗證提供者要求的預設範圍之外的其他 OAuth 2.0 範圍。

    provider_data.scopes.push_back("email");
    provider_data.scopes.push_back("name");
    
  3. 可選:如果您想以英語以外的語言顯示 Apple 的登入螢幕,請設定locale參數。請參閱「使用 Apple 登入」文件以了解支援的區域設定。

    // Localize to French.
    provider_data.custom_parameters["language"] = "fr";
    ```
    
  4. 配置提供者資料後,使用它來建立 FederatedOAuthProvider。

    // Construct a FederatedOAuthProvider for use in Auth methods.
    firebase::auth::FederatedOAuthProvider provider(provider_data);
    
  5. 使用身份驗證提供者物件透過 Firebase 進行身份驗證。請注意,與其他 FirebaseAuth 操作不同,這將透過彈出一個 Web 視圖(使用者可以在其中輸入其憑證)來控制您的 UI。

    若要啟動登入流程,請呼叫signInWithProvider

    firebase::Future<firebase::auth::AuthResult> result =
      auth->SignInWithProvider(provider_data);
    

    然後,您的應用程式可能會等待或在 Future 上註冊回調

  6. 相同的模式可以與ReauthenticateWithProvider一起使用,它可用於檢索需要最近登入的敏感操作的新憑證。

    firebase::Future<firebase::auth::AuthResult> result =
      user.ReauthenticateWithProvider(provider_data);
    

    然後,您的應用程式可能會等待或在 Future 上註冊回調

  7. 並且,您可以使用LinkWithCredential()將不同的身分提供者連結到現有帳戶。

    請注意,Apple 要求您在將用戶的 Apple 帳戶連結到其他資料之前獲得用戶的明確同意。

    例如,要將 Facebook 帳戶連結到目前 Firebase 帳戶,請使用使用者登入 Facebook 時獲得的存取權杖:

    // Initialize a Facebook credential with a Facebook access token.
    AuthCredential credential =
        firebase::auth::FacebookAuthProvider.getCredential(token);
    
    // Assuming the current user is an Apple user linking a Facebook provider.
    firebase::Future<firebase::auth::AuthResult> result =
        auth.current_user().LinkWithCredential(credential);
    

使用 Apple Notes 登入

與 Firebase Auth 支援的其他提供者不同,Apple 不提供照片 URL。

此外,當使用者選擇不與應用程式分享其電子郵件時,Apple 會為該使用者提供一個獨特的電子郵件地址(格式為xyz@privaterelay.appleid.com ),並與您的應用程式共用。如果您設定了私人電子郵件轉發服務,Apple 會將發送到匿名地址的電子郵件轉發到使用者的真實電子郵件地址。

Apple 僅在使用者首次登入時與應用程式共用使用者訊息,例如顯示名稱。通常,Firebase 會在使用者首次登入 Apple 時儲存顯示名稱,您可以透過current_user().display_name()取得該顯示名稱。但是,如果您之前使用 Apple 將使用者登入應用程式而不使用 Firebase,則 Apple 不會向 Firebase 提供使用者的顯示名稱。

下一步

使用者首次登入後,系統會建立新的使用者帳戶,並將其連結到使用者登入時所使用的憑證(即使用者名稱和密碼、電話號碼或驗證提供者資訊)。此新帳戶將作為 Firebase 專案的一部分存儲,並且可用於識別專案中每個應用程式中的用戶,無論用戶如何登入。

在您的應用程式中,您可以從firebase::auth::User物件取得使用者的基本個人資料資訊。請參閱管理用戶

在 Firebase 即時資料庫和雲端儲存安全性規則中,您可以從 auth 變數取得登入使用者的唯一使用者 ID,並使用它來控制使用者可以存取哪些資料。