使用 Apple 和 C++ 進行驗證

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

事前準備

如要使用 Apple 登入使用者,請先在 Apple 開發人員網站上設定「透過 Apple 登入」,然後為 Firebase 專案啟用 Apple 做為登入供應商。

加入 Apple 開發人員計畫

只有 Apple 開發人員計畫的成員可以設定「使用 Apple 帳戶登入」。

設定「使用 Apple 登入」功能

您必須在 Firebase 專案中啟用 Apple 登入功能,並正確設定。Android 和 Apple 平台的設定方式不同。請先按照Apple 平台和/或 Android 指南的「設定使用 Apple 登入」一節操作,再繼續進行。

啟用 Apple 做為登入供應商

  1. Firebase 控制台中,依序前往「安全性」>「驗證」
  2. 在「登入方式」分頁中,啟用「Apple」登入供應商。
  3. 設定 Apple 登入提供者設定:
    • Apple:如果只在 Apple 平台上部署應用程式,可以將服務 ID、Apple Team ID、私密金鑰和金鑰 ID 欄位留空。
    • Android:完成下列步驟,支援 Android 裝置:
      1. 將 Firebase 新增至 Android 專案
      2. 指定應用程式的 SHA-1 指紋 (如果尚未指定)。
        1. Firebase 控制台中,依序前往 「設定」 > 「一般」分頁標籤
        2. 向下捲動至「您的應用程式」資訊卡,選取您的 Android 應用程式,然後在「SHA 憑證指紋」欄位中新增 SHA-1 指紋。

        如要瞭解如何取得應用程式的 SHA 指紋,請參閱「驗證用戶端」一文。

      3. 設定 Apple 登入提供者設定:
        1. Firebase 控制台中,依序前往「安全性」 > 「驗證」
        2. 在「登入方式」分頁中,按一下「Apple」登入供應商。
        3. 指定您在上一節建立的服務 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::Appfirebase::auth::Auth 類別。 AppAuth 之間是一對一的對應關係。
    firebase::auth::Auth* auth = firebase::auth::Auth::GetAuth(app);

使用 Firebase SDK 處理登入流程

「使用 Apple 帳戶登入」的程序會因 Apple 和 Android 平台而異。

在 Apple 平台上

透過 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--;
            }
          }
        }
      }
    
    

    您會在登入要求中傳送 Nonce 的 SHA256 雜湊,Apple 會在回應中不變地傳遞該雜湊。Firebase 會將原始 Nonce 雜湊處理,並與 Apple 傳遞的值進行比較,藉此驗證回應。

  2. 啟動 Apple 的登入流程,包括在要求中加入 Nonce 的 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 Credential 並登入 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 登入帳戶。不過,如果現有的 Firebase 帳戶已連結至您嘗試連結的 Apple 帳戶,您可能會遇到錯誤。發生這種情況時,Future 會傳回 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 版

在 Android 上,請使用 Firebase SDK 將網頁型一般 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. 使用 Auth 提供者物件向 Firebase 進行驗證。請注意,與其他 FirebaseAuth 作業不同,這項作業會彈出網頁檢視畫面,讓使用者輸入憑證,藉此控管您的 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 不會提供相片網址。

此外,如果使用者選擇不與應用程式分享電子郵件地址,Apple 會為該使用者提供專屬電子郵件地址 (格式為 xyz@privaterelay.appleid.com),並與您的應用程式分享。如果您已設定私人電子郵件轉發服務,Apple 會將傳送至匿名地址的電子郵件轉寄至使用者的實際電子郵件地址。

Apple 只會在使用者首次登入時,與應用程式分享顯示名稱等使用者資訊。通常,Firebase 會在使用者首次透過 Apple 登入時儲存顯示名稱,您可以透過 current_user().display_name() 取得該名稱。不過,如果您先前使用 Apple 登入應用程式,但並未採用 Firebase,Apple 就不會提供使用者的顯示名稱給 Firebase。

後續步驟

使用者首次登入後,系統會建立新的使用者帳戶,並連結至使用者登入時使用的憑證 (即使用者名稱和密碼、電話號碼或驗證供應商資訊)。這個新帳戶會儲存在 Firebase 專案中,可用於識別專案中每個應用程式的使用者,無論使用者登入方式為何。

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

在 Firebase 即時資料庫和 Cloud Storage 安全性規則中,您可以從 auth 變數取得登入使用者的專屬使用者 ID,並用來控管使用者可存取的資料。