在 Android 上使用 Apple 驗證

您可以讓使用者透過 Apple ID 向 Firebase 進行驗證,步驟如下: 並使用 Firebase SDK 執行端對端 OAuth 2.0 登入流程。

事前準備

如要使用 Apple 登入使用者,請先設定「使用 Apple 帳戶登入」 然後啟用 Apple 做為您的登入服務供應商, Firebase 專案。

加入 Apple Developer Program

只有 Apple Developer 成員可以設定「使用 Apple 帳戶登入」 計畫

設定「使用 Apple 登入」功能

Apple 裝置上 開發人員網站中,執行下列操作:

  1. 按照第一節的說明,為您的網站與應用程式建立關聯 / 為網頁版設定「使用 Apple 帳戶登入」。系統提示時,請註冊 將下列網址做為傳回網址:

    https://YOUR_FIREBASE_PROJECT_ID.firebaseapp.com/__/auth/handler

    您可以在 Firebase 控制台 設定頁面

    完成後,請記下新的服務 ID,以備不時之需 請參閱下一節的說明。

  2. 建立 使用 Apple 私密金鑰登入。您需要新的私密金鑰和金鑰 ID。
  3. 如果您使用的 Firebase 驗證功能會傳送電子郵件給使用者 包括電子郵件連結登入、電子郵件地址驗證、帳戶變更 撤銷 其他, 設定 Apple 私人電子郵件轉發服務並註冊 noreply@YOUR_FIREBASE_PROJECT_ID.firebaseapp.com (或您的自訂電子郵件範本網域),讓 Apple 可以轉發傳送的電子郵件 因為 Firebase 驗證取得匿名的 Apple 電子郵件地址。

啟用 Apple 做為登入服務供應商

  1. 將 Firebase 新增至您的 Android 專案。成為 請務必使用應用程式的 SHA-1 簽章,在 Firebase 控制台。
  2. Firebase 控制台開啟「驗證」專區。在「Sign in method」分頁中: 啟用 Apple 供應商。 指定您在上一節建立的服務 ID。此外,在 OAuth 程式碼流程設定部分,指定您的 Apple Team ID,然後 私密金鑰和金鑰 ID

遵守 Apple 的去識別化資料規定

「使用 Apple 帳戶登入」功能可讓使用者選擇將資料去識別化、 包括電子郵件地址選擇這個選項的使用者 擁有 privaterelay.appleid.com 網域的電子郵件地址。時間 如果您在應用程式中使用「使用 Apple 帳戶登入」功能,您必須遵守所有適用的 有關這些匿名 Apple 的開發人員政策或條款 而非客戶 ID

包括事先取得任何必要的使用者同意聲明 將任何直接識別的個人資訊與去識別化的 Apple 建立關聯 編號。使用 Firebase 驗證時,這可能包括: 動作:

  • 將電子郵件地址連結至去識別化的 Apple ID。
  • 將電話號碼與去識別化的 Apple ID 連結
  • 將非匿名的社會憑證 (Facebook、Google 等) 連結至 或匿名化 Apple ID。

請注意,上方清單僅列出部分示例。參閱 Apple Developer Program 《授權協議》 確認您的應用程式符合 Apple 的規定。

使用 Firebase SDK 處理登入流程

在 Android 上,最簡單的方法就是使用 Apple 帳戶可透過 Firebase Android 處理整個登入流程 將機器學習工作流程自動化

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

  1. 使用建構工具搭配OAuthProvider 提供者 ID apple.com

    Kotlin+KTX

    val provider = OAuthProvider.newBuilder("apple.com")
    

    Java

    OAuthProvider.Builder provider = OAuthProvider.newBuilder("apple.com");
    
  2. 選用:指定除了預設 設為要向驗證服務供應商發出要求

    Kotlin+KTX

    provider.setScopes(arrayOf("email", "name"))
    

    Java

    List<String> scopes =
        new ArrayList<String>() {
          {
            add("email");
            add("name");
          }
        };
    provider.setScopes(scopes);
    

    根據預設,如果啟用「每個電子郵件地址一個帳戶」,Firebase 就會是 要求提供電子郵件地址和名稱範圍。如果將這項設定變更為多重 帳戶每個電子郵件地址的帳戶,Firebase 不會向 Apple 要求任何範圍 除非您指定這些項目

  3. 選用:如要以特定語言顯示 Apple 的登入畫面 ,請設定 locale 參數。詳情請參閱 使用 Apple 登入說明文件 支援語言代碼

    Kotlin+KTX

    // Localize the Apple authentication screen in French.
    provider.addCustomParameter("locale", "fr")
    

    Java

    // Localize the Apple authentication screen in French.
    provider.addCustomParameter("locale", "fr");
    
  4. 使用 OAuth 提供者物件向 Firebase 進行驗證。請注意,這不像 其他 FirebaseAuth 作業,執行這項操作後,系統就會透過 開啟「自訂 Chrome 分頁」。因此,請勿在 您附加的 OnSuccessListenerOnFailureListener 在作業啟動 UI 時立即卸離。

    請先檢查是否已收到回覆。登入中 此方法會將活動放在背景,這表示 但可在登入流程中收回。如想 如果發生這種情形,請勿讓使用者再次嘗試 檢查結果是否已經存在。

    如要查看是否有待處理的結果,請呼叫 getPendingAuthResult()

    Kotlin+KTX

    val pending = auth.pendingAuthResult
    if (pending != null) {
        pending.addOnSuccessListener { authResult ->
            Log.d(TAG, "checkPending:onSuccess:$authResult")
            // Get the user profile with authResult.getUser() and
            // authResult.getAdditionalUserInfo(), and the ID
            // token from Apple with authResult.getCredential().
        }.addOnFailureListener { e ->
            Log.w(TAG, "checkPending:onFailure", e)
        }
    } else {
        Log.d(TAG, "pending: null")
    }
    

    Java

    mAuth = FirebaseAuth.getInstance();
    Task<AuthResult> pending = mAuth.getPendingAuthResult();
    if (pending != null) {
        pending.addOnSuccessListener(new OnSuccessListener<AuthResult>() {
            @Override
            public void onSuccess(AuthResult authResult) {
                Log.d(TAG, "checkPending:onSuccess:" + authResult);
                // Get the user profile with authResult.getUser() and
                // authResult.getAdditionalUserInfo(), and the ID
                // token from Apple with authResult.getCredential().
            }
        }).addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                Log.w(TAG, "checkPending:onFailure", e);
            }
        });
    } else {
        Log.d(TAG, "pending: null");
    }
    

    如果沒有待處理結果,請呼叫 startActivityForSignInWithProvider():

    Kotlin+KTX

    auth.startActivityForSignInWithProvider(this, provider.build())
            .addOnSuccessListener { authResult ->
                // Sign-in successful!
                Log.d(TAG, "activitySignIn:onSuccess:${authResult.user}")
                val user = authResult.user
                // ...
            }
            .addOnFailureListener { e ->
                Log.w(TAG, "activitySignIn:onFailure", e)
            }
    

    Java

    mAuth.startActivityForSignInWithProvider(this, provider.build())
            .addOnSuccessListener(
                    new OnSuccessListener<AuthResult>() {
                        @Override
                        public void onSuccess(AuthResult authResult) {
                            // Sign-in successful!
                            Log.d(TAG, "activitySignIn:onSuccess:" + authResult.getUser());
                            FirebaseUser user = authResult.getUser();
                            // ...
                        }
                    })
            .addOnFailureListener(
                    new OnFailureListener() {
                        @Override
                        public void onFailure(@NonNull Exception e) {
                            Log.w(TAG, "activitySignIn:onFailure", e);
                        }
                    });
    

    不同於 Firebase Auth 支援的其他供應商,Apple 不提供 相片網址。

    此外,如果使用者選擇不與應用程式分享電子郵件地址,Apple 會為他佈建一個專屬電子郵件地址 (格式為 xyz@privaterelay.appleid.com) 與應用程式共用。如果發生以下情況: 設定私人電子郵件轉發服務後,Apple 會轉寄傳送到 傳送至使用者實際電子郵件地址的匿名地址

    Apple 只會將使用者資訊 (例如顯示名稱) 提供給應用程式 使用者初次登入時Firebase 通常會儲存 初次透過 Apple 帳戶登入時,您可在 getCurrentUser().getDisplayName()。 不過,如果您先前曾使用 Apple 登入應用程式 使用 Firebase 時,Apple 不會提供 Firebase 使用者的螢幕畫面 名稱。

重新驗證和帳戶連結

相同的模式可與 startActivityForReauthenticateWithProvider() 搭配使用 您可用它針對敏感作業擷取新憑證 需要最近登入:

Kotlin+KTX

// The user is already signed-in.
val firebaseUser = auth.getCurrentUser()

firebaseUser
    .startActivityForReauthenticateWithProvider(/* activity= */ this, provider.build())
    .addOnSuccessListener( authResult -> {
        // User is re-authenticated with fresh tokens and
        // should be able to perform sensitive operations
        // like account deletion and email or password
        // update.
    })
    .addOnFailureListener( e -> {
        // Handle failure.
    })

Java

// The user is already signed-in.
FirebaseUser firebaseUser = mAuth.getCurrentUser();

firebaseUser
    .startActivityForReauthenticateWithProvider(/* activity= */ this, provider.build())
    .addOnSuccessListener(
        new OnSuccessListener<AuthResult>() {
          @Override
          public void onSuccess(AuthResult authResult) {
            // User is re-authenticated with fresh tokens and
            // should be able to perform sensitive operations
            // like account deletion and email or password
            // update.
          }
        })
    .addOnFailureListener(
        new OnFailureListener() {
          @Override
          public void onFailure(@NonNull Exception e) {
            // Handle failure.
          }
        });

也能使用 linkWithCredential() 連結不同的識別資訊提供者 現有帳戶。

請注意,根據 Apple 的規定,您必須先取得使用者的明確同意,才能進行連結 將 Apple 帳戶轉移至其他資料。

舉例來說,如要將 Facebook 帳戶連結至目前的 Firebase 帳戶,請使用 因使用者登入 Facebook 而取得的存取權杖:

Kotlin+KTX

// Initialize a Facebook credential with a Facebook access token.
val credential = FacebookAuthProvider.getCredential(token.getToken())

// Assuming the current user is an Apple user linking a Facebook provider.
mAuth.getCurrentUser().linkWithCredential(credential)
    .addOnCompleteListener(this, task -> {
        if (task.isSuccessful()) {
          // Facebook credential is linked to the current Apple user.
          // The user can now sign in to the same account
          // with either Apple or Facebook.
        }
      });

Java

// Initialize a Facebook credential with a Facebook access token.
AuthCredential credential = FacebookAuthProvider.getCredential(token.getToken());

// Assuming the current user is an Apple user linking a Facebook provider.
mAuth.getCurrentUser().linkWithCredential(credential)
    .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
      @Override
      public void onComplete(@NonNull Task<AuthResult> task) {
        if (task.isSuccessful()) {
          // Facebook credential is linked to the current Apple user.
          // The user can now sign in to the same account
          // with either Apple or Facebook.
        }
      }
    });

進階:手動處理登入流程

您也可以使用 Apple 帳戶進行 Firebase 驗證,方法是處理 登入流程,方法是使用 Apple Sign-In JS SDK,然後手動建構 OAuth 流程,或是使用 OAuth 程式庫,例如 AppAuth

  1. 針對每個登入要求產生隨機字串— 「nonce」,您可以用來確保取得的 ID 權杖 。這個 步驟非常重要,即可預防重送攻擊

    您可以在 Android 裝置上透過以下指令產生加密的安全 Nonce SecureRandom,如以下範例所示:

    Kotlin+KTX

    private fun generateNonce(length: Int): String {
        val generator = SecureRandom()
    
        val charsetDecoder = StandardCharsets.US_ASCII.newDecoder()
        charsetDecoder.onUnmappableCharacter(CodingErrorAction.IGNORE)
        charsetDecoder.onMalformedInput(CodingErrorAction.IGNORE)
    
        val bytes = ByteArray(length)
        val inBuffer = ByteBuffer.wrap(bytes)
        val outBuffer = CharBuffer.allocate(length)
        while (outBuffer.hasRemaining()) {
            generator.nextBytes(bytes)
            inBuffer.rewind()
            charsetDecoder.reset()
            charsetDecoder.decode(inBuffer, outBuffer, false)
        }
        outBuffer.flip()
        return outBuffer.toString()
    }
    

    Java

    private String generateNonce(int length) {
        SecureRandom generator = new SecureRandom();
    
        CharsetDecoder charsetDecoder = StandardCharsets.US_ASCII.newDecoder();
        charsetDecoder.onUnmappableCharacter(CodingErrorAction.IGNORE);
        charsetDecoder.onMalformedInput(CodingErrorAction.IGNORE);
    
        byte[] bytes = new byte[length];
        ByteBuffer inBuffer = ByteBuffer.wrap(bytes);
        CharBuffer outBuffer = CharBuffer.allocate(length);
        while (outBuffer.hasRemaining()) {
            generator.nextBytes(bytes);
            inBuffer.rewind();
            charsetDecoder.reset();
            charsetDecoder.decode(inBuffer, outBuffer, false);
        }
        outBuffer.flip();
        return outBuffer.toString();
    }
    

    然後,以十六進位字串的形式取得 Nonce 的 SHA246 雜湊:

    Kotlin+KTX

    private fun sha256(s: String): String {
        val md = MessageDigest.getInstance("SHA-256")
        val digest = md.digest(s.toByteArray())
        val hash = StringBuilder()
        for (c in digest) {
            hash.append(String.format("%02x", c))
        }
        return hash.toString()
    }
    

    Java

    private String sha256(String s) throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] digest = md.digest(s.getBytes());
        StringBuilder hash = new StringBuilder();
        for (byte c: digest) {
            hash.append(String.format("%02x", c));
        }
        return hash.toString();
    }
    

    您會在登入要求時傳送 Nonce 的 SHA256 雜湊, Apple 會在回應中傳遞無變更。Firebase 會驗證回應 ,方法是對原始 Nonce 雜湊,並與 Apple 傳送的值進行比較。

  2. 使用 OAuth 程式庫或其他方法啟動 Apple 登入流程。成為 請務必在要求中加入經雜湊處理的 Nonce 做為參數。

  3. 收到 Apple 的回應後,請取得回應中的 ID 權杖和 使用這個函式和未雜湊處理的 Nonce 來建立 AuthCredential

    Kotlin+KTX

    val credential =  OAuthProvider.newCredentialBuilder("apple.com")
        .setIdTokenWithRawNonce(appleIdToken, rawUnhashedNonce)
        .build()
    

    Java

    AuthCredential credential =  OAuthProvider.newCredentialBuilder("apple.com")
        .setIdTokenWithRawNonce(appleIdToken, rawUnhashedNonce)
        .build();
    
  4. 使用 Firebase 憑證向 Firebase 進行驗證:

    Kotlin+KTX

    auth.signInWithCredential(credential)
          .addOnCompleteListener(this) { task ->
              if (task.isSuccessful) {
                // User successfully signed in with Apple ID token.
                // ...
              }
          }
    

    Java

    mAuth.signInWithCredential(credential)
        .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
          @Override
          public void onComplete(@NonNull Task<AuthResult> task) {
            if (task.isSuccessful()) {
              // User successfully signed in with Apple ID token.
              // ...
            }
          }
        });
    

如果呼叫 signInWithCredential 成功,您可以使用 getCurrentUser 取得使用者帳戶資料的方法。

撤銷權杖

根據 Apple 規定,如果應用程式設有帳戶建立功能,就必須讓使用者啟動 根據 App Store 審查 準則

此外,支援「使用 Apple 帳戶登入」功能的應用程式應使用「使用 Apple 帳戶登入」功能 撤銷使用者權杖的 REST API。

為符合這項規定,請實作下列步驟:

  1. 使用 startActivityForSignInWithProvider() 方法使用 Apple 登入並取得 AuthResult

  2. 取得 Apple 提供者的存取權杖。

    Kotlin+KTX

    val oauthCredential: OAuthCredential =  authResult.credential
    val accessToken = oauthCredential.accessToken
    

    Java

    OAuthCredential oauthCredential = (OAuthCredential) authResult.getCredential();
    String accessToken = oauthCredential.getAccessToken();
    
  3. 使用 revokeAccessToken API 撤銷權杖。

    Kotlin+KTX

    mAuth.revokeAccessToken(accessToken)
      .addOnCompleteListener(this) { task ->
        if (task.isSuccessful) {
          // Access token successfully revoked
          // for the user ...
        }
    }
    

    Java

    mAuth.revokeAccessToken(accessToken)
        .addOnCompleteListener(this, new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
              if (task.isSuccessful()) {
                // Access token successfully revoked
                // for the user ...
              }
            }
      });
    
  1. 最後,刪除使用者帳戶 (和 相關資料)

    後續步驟

    使用者首次登入後,系統會建立新的使用者帳戶 也就是使用者的名稱和密碼 號碼或驗證提供者資訊,也就是使用者登入時使用的網址。這項新功能 帳戶儲存為 Firebase 專案的一部分,可用來識別 即可限制使用者登入專案中的所有應用程式

    • 在您的應用程式中,您可以透過 FirebaseUser 物件。詳情請參閱 管理使用者。

    • 在您的 Firebase 即時資料庫和 Cloud Storage 中 查看安全性規則即可 透過 auth 變數取得已登入使用者的不重複使用者 ID。 控管使用者可以存取的資料

    您可以讓使用者透過多重驗證機制登入您的應用程式 將驗證供應商憑證連結至 現有的使用者帳戶

    如要登出使用者,請呼叫 signOut

    Kotlin+KTX

    Firebase.auth.signOut()

    Java

    FirebaseAuth.getInstance().signOut();