您可以使用 Firebase SDK 執行端對端 OAuth 2.0 登入流程,讓使用者透過 Apple ID 向 Firebase 驗證身分。
事前準備
如要使用 Apple 登入使用者,請先在 Apple 開發人員網站上設定「透過 Apple 登入」,然後為 Firebase 專案啟用 Apple 做為登入供應商。
加入 Apple 開發人員計畫
只有 Apple 開發人員計畫的成員可以設定「使用 Apple 帳戶登入」。
設定「使用 Apple 登入」功能
在 Apple 開發人員網站上執行下列操作:
-
按照「 為網站設定 Apple 登入功能」一文第一節的說明,將網站與應用程式建立關聯。在系統提示時,請將下列網址註冊為返回網址:
https://YOUR_FIREBASE_PROJECT_ID.firebaseapp.com/__/auth/handler
您可以在 Firebase 控制台的「
設定 」>「一般」分頁標籤中找到 Firebase 專案 ID。完成後,請記下新的服務 ID,下節會用到。
- 建立「使用 Apple 帳戶登入」私密金鑰。下一節會需要用到新的私密金鑰和金鑰 ID。
-
如果您使用 Firebase Authentication 的任何功能傳送電子郵件給使用者,包括電子郵件連結登入、電子郵件地址驗證、帳戶變更撤銷等,請 設定 Apple 私人電子郵件轉發服務,並註冊
noreply@YOUR_FIREBASE_PROJECT_ID.firebaseapp.com(或自訂電子郵件範本網域),讓 Apple 將 Firebase Authentication 傳送的電子郵件轉發至匿名 Apple 電子郵件地址。
啟用 Apple 做為登入供應商
- 將 Firebase 新增至 Android 專案。
-
指定應用程式的 SHA-1 指紋 (如果尚未指定)。
-
在 Firebase 控制台中,依序前往「
設定 」>「一般」分頁標籤。 - 向下捲動至「您的應用程式」資訊卡,選取您的 Android 應用程式,然後在「SHA 憑證指紋」欄位中新增 SHA-1 指紋。
-
在 Firebase 控制台中,依序前往「
- 在 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 SDK 處理登入流程
在 Android 上,如要使用者的 Apple 帳戶透過 Firebase 驗證身分,最簡單的方法是使用 Firebase Android SDK 處理整個登入流程。
如要使用 Firebase Android SDK 處理登入流程,請按照下列步驟操作:
使用建構工具和供應商 ID
apple.com,建構OAuthProvider的執行個體:Kotlin
val provider = OAuthProvider.newBuilder("apple.com")Java
OAuthProvider.Builder provider = OAuthProvider.newBuilder("apple.com");選用:指定要向驗證供應商要求預設範圍以外的其他 OAuth 2.0 範圍。
Kotlin
provider.setScopes(arrayOf("email", "name"))Java
List<String> scopes = new ArrayList<String>() { { add("email"); add("name"); } }; provider.setScopes(scopes);根據預設,啟用「每個電子郵件地址一個帳戶」後,Firebase 會要求電子郵件和名稱範圍。如果將這項設定變更為「每個電子郵件地址有多個帳戶」,除非您指定範圍,否則 Firebase 不會向 Apple 要求任何範圍。
選用:如要以英文以外的語言顯示 Apple 登入畫面,請設定
locale參數。如需支援的語言代碼,請參閱「使用 Apple 登入」文件。Kotlin
// Localize the Apple authentication screen in French. provider.addCustomParameter("locale", "fr")Java
// Localize the Apple authentication screen in French. provider.addCustomParameter("locale", "fr");使用 OAuth 供應商物件向 Firebase 進行驗證。請注意,與其他
FirebaseAuth作業不同,這項作業會開啟 Chrome 自訂分頁,藉此控管 UI。因此,請勿在您附加的OnSuccessListener和OnFailureListener中參照 Activity,因為作業啟動 UI 時,這些項目會立即分離。請先確認是否已收到回覆。使用這種方法登入時,活動會進入背景,也就是說,系統可以在登入流程中回收活動。為確保使用者不會在發生這種情況時重試,您應檢查是否已存在結果。
如要檢查是否有待處理的結果,請呼叫
getPendingAuthResult():Kotlin
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
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
// 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
// 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 流程,或是使用 AppAuth 等 OAuth 程式庫。
針對每個登入要求,產生隨機字串 (即「nonce」),確保您取得的 ID 權杖是專為回應應用程式的驗證要求而授予。這個步驟非常重要,可防範重送攻擊。
您可以在 Android 上使用
SecureRandom產生加密安全隨機數,如下列範例所示:Kotlin
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(); }接著,以十六進位字串形式取得隨機碼的 SHA246 雜湊:
Kotlin
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 傳遞的值進行比較,藉此驗證回應。
使用 OAuth 程式庫或其他方法啟動 Apple 的登入流程。請務必在要求中加入雜湊的隨機值做為參數。
收到 Apple 的回覆後,請從回覆中取得 ID 權杖,並使用該權杖和未經過雜湊處理的 Nonce 建立
AuthCredential:Kotlin
val credential = OAuthProvider.newCredentialBuilder("apple.com") .setIdTokenWithRawNonce(appleIdToken, rawUnhashedNonce) .build()Java
AuthCredential credential = OAuthProvider.newCredentialBuilder("apple.com") .setIdTokenWithRawNonce(appleIdToken, rawUnhashedNonce) .build();使用 Firebase 憑證向 Firebase 進行驗證:
Kotlin
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 方法取得使用者的帳戶資料。
權杖撤銷
根據 App Store 審查指南,Apple 規定應用程式若設有帳戶建立機制,就必須讓使用者能從應用程式中啟動帳戶刪除程序。
此外,支援「透過 Apple 登入」的應用程式應使用「透過 Apple 登入」REST API 撤銷使用者權杖。
如要符合這項規定,請按照下列步驟操作:
使用
startActivityForSignInWithProvider()方法透過 Apple 登入,並取得AuthResult。取得 Apple 供應商的存取權杖。
Kotlin
val oauthCredential: OAuthCredential = authResult.credential val accessToken = oauthCredential.accessTokenJava
OAuthCredential oauthCredential = (OAuthCredential) authResult.getCredential(); String accessToken = oauthCredential.getAccessToken();使用
revokeAccessTokenAPI 撤銷權杖。Kotlin
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 ... } } });
- 最後,刪除使用者帳戶 (和所有相關聯的資料)
後續步驟
使用者首次登入後,系統會建立新的使用者帳戶,並連結至使用者登入時使用的憑證 (即使用者名稱和密碼、電話號碼或驗證供應商資訊)。這個新帳戶會儲存在 Firebase 專案中,可用於識別專案中每個應用程式的使用者,無論使用者登入方式為何。
-
在應用程式中,您可以從
FirebaseUser物件取得使用者的基本個人資料資訊。請參閱「 管理使用者」。 在 Firebase Realtime Database 和 Cloud Storage 安全規則中,您可以從
auth變數取得已登入使用者的專屬使用者 ID, 並使用該 ID 控制使用者可存取的資料。
您可以將驗證供應商憑證連結至現有使用者帳戶,允許使用者透過多個驗證供應商登入應用程式。
如要登出使用者,請呼叫
signOut:Kotlin
Firebase.auth.signOut()
Java
FirebaseAuth.getInstance().signOut();
-