您可以使用 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 新增至專案。
- 在 Firebase 控制台中,依序前往「安全性」>「驗證」。
- 在「Sign-in method」(登入方式) 分頁中,啟用「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 處理登入流程
如果您要建構網頁應用程式,最簡單的方法是使用 Firebase JavaScript SDK 處理整個登入流程,讓使用者透過 Apple 帳戶驗證身分。
如要使用 Firebase JavaScript SDK 處理登入流程,請按照下列步驟操作:
使用對應的供應商 ID apple.com,建立 OAuthProvider 的執行個體。
Web
import { OAuthProvider } from "firebase/auth"; const provider = new OAuthProvider('apple.com');
Web
var provider = new firebase.auth.OAuthProvider('apple.com');
選用:指定要向驗證供應商要求,但預設範圍以外的其他 OAuth 2.0 範圍。
Web
provider.addScope('email'); provider.addScope('name');
Web
provider.addScope('email'); provider.addScope('name');
根據預設,啟用「每個電子郵件地址一個帳戶」時,Firebase 會要求電子郵件和名稱範圍。如果將這項設定變更為「每個電子郵件地址多個帳戶」,除非您指定範圍,否則 Firebase 不會向 Apple 要求任何範圍。
選用:如要以英文以外的語言顯示 Apple 登入畫面,請設定
locale參數。如需支援的語言代碼,請參閱「透過 Apple 登入」文件。Web
provider.setCustomParameters({ // Localize the Apple authentication screen in French. locale: 'fr' });
Web
provider.setCustomParameters({ // Localize the Apple authentication screen in French. locale: 'fr' });
使用 OAuth 提供者物件向 Firebase 進行驗證。您可以開啟彈出式視窗或重新導向至登入頁面,提示使用者透過 Apple 帳戶登入。在行動裝置上,建議使用重新導向方法。
如要使用彈出式視窗登入,請呼叫
signInWithPopup():Web
import { getAuth, signInWithPopup, OAuthProvider } from "firebase/auth"; const auth = getAuth(); signInWithPopup(auth, provider) .then((result) => { // The signed-in user info. const user = result.user; // Apple credential const credential = OAuthProvider.credentialFromResult(result); const accessToken = credential.accessToken; const idToken = credential.idToken; // IdP data available using getAdditionalUserInfo(result) // ... }) .catch((error) => { // Handle Errors here. const errorCode = error.code; const errorMessage = error.message; // The email of the user's account used. const email = error.customData.email; // The credential that was used. const credential = OAuthProvider.credentialFromError(error); // ... });
Web
firebase .auth() .signInWithPopup(provider) .then((result) => { /** @type {firebase.auth.OAuthCredential} */ var credential = result.credential; // The signed-in user info. var user = result.user; // You can also get the Apple OAuth Access and ID Tokens. var accessToken = credential.accessToken; var idToken = credential.idToken; // IdP data available using getAdditionalUserInfo(result) // ... }) .catch((error) => { // Handle Errors here. var errorCode = error.code; var errorMessage = error.message; // The email of the user's account used. var email = error.email; // The firebase.auth.AuthCredential type that was used. var credential = error.credential; // ... });
如要透過重新導向至登入頁面的方式登入,請呼叫
signInWithRedirect():
使用
signInWithRedirect、linkWithRedirect或reauthenticateWithRedirect時,請遵循最佳做法。Web
import { getAuth, signInWithRedirect } from "firebase/auth"; const auth = getAuth(); signInWithRedirect(auth, provider);
Web
firebase.auth().signInWithRedirect(provider);
使用者完成登入並返回頁面後,您可以呼叫
getRedirectResult()取得登入結果:Web
import { getAuth, getRedirectResult, OAuthProvider } from "firebase/auth"; // Result from Redirect auth flow. const auth = getAuth(); getRedirectResult(auth) .then((result) => { const credential = OAuthProvider.credentialFromResult(result); if (credential) { // You can also get the Apple OAuth Access and ID Tokens. const accessToken = credential.accessToken; const idToken = credential.idToken; } // The signed-in user info. const user = result.user; }) .catch((error) => { // Handle Errors here. const errorCode = error.code; const errorMessage = error.message; // The email of the user's account used. const email = error.customData.email; // The credential that was used. const credential = OAuthProvider.credentialFromError(error); // ... });
Web
// Result from Redirect auth flow. firebase .auth() .getRedirectResult() .then((result) => { if (result.credential) { /** @type {firebase.auth.OAuthCredential} */ var credential = result.credential; // You can get the Apple OAuth Access and ID Tokens. var accessToken = credential.accessToken; var idToken = credential.idToken; // IdP data available in result.additionalUserInfo.profile. // ... } // The signed-in user info. var user = result.user; }) .catch((error) => { // Handle Errors here. var errorCode = error.code; var errorMessage = error.message; // The email of the user's account used. var email = error.email; // The firebase.auth.AuthCredential type that was used. var credential = error.credential; // ... });
您也可以在這裡擷取及處理錯誤。如需錯誤代碼清單,請參閱 API 參考資料。
與 Firebase Auth 支援的其他供應商不同,Apple 不提供相片網址。
此外,如果使用者選擇不與應用程式分享電子郵件地址,Apple 會為該使用者提供專屬電子郵件地址 (格式為
xyz@privaterelay.appleid.com),並與您的應用程式分享。如果您已設定私人電子郵件轉發服務,Apple 會將傳送至匿名地址的電子郵件轉寄給使用者的實際電子郵件地址。Apple 只會在使用者首次登入時,與應用程式分享顯示名稱等使用者資訊。通常,Firebase 會在使用者首次透過 Apple 登入時儲存顯示名稱,您可以透過
firebase.auth().currentUser.displayName取得該名稱。不過,如果您先前未使用 Firebase,而是透過 Apple 登入應用程式,Apple 就不會向 Firebase 提供使用者的顯示名稱。
重新驗證及連結帳戶
您也可以將相同模式用於 reauthenticateWithPopup() 和 reauthenticateWithRedirect(),以便擷取新憑證,用於需要最近登入的敏感作業:
Web
import { getAuth, reauthenticateWithPopup, OAuthProvider } from "firebase/auth"; // Result from Redirect auth flow. const auth = getAuth(); const provider = new OAuthProvider('apple.com'); reauthenticateWithPopup(auth.currentUser, provider) .then((result) => { // User is re-authenticated with fresh tokens minted and can perform // sensitive operations like account deletion, or updating their email // address or password. // The signed-in user info. const user = result.user; // You can also get the Apple OAuth Access and ID Tokens. const credential = OAuthProvider.credentialFromResult(result); const accessToken = credential.accessToken; const idToken = credential.idToken; // ... }) .catch((error) => { // Handle Errors here. const errorCode = error.code; const errorMessage = error.message; // The email of the user's account used. const email = error.customData.email; // The credential that was used. const credential = OAuthProvider.credentialFromError(error); // ... });
Web
const provider = new firebase.auth.OAuthProvider('apple.com'); firebase .auth() .currentUser .reauthenticateWithPopup(provider) .then((result) => { // User is re-authenticated with fresh tokens minted and can perform // sensitive operations like account deletion, or updating their email // address or password. /** @type {firebase.auth.OAuthCredential} */ var credential = result.credential; // The signed-in user info. var user = result.user; // You can also get the Apple OAuth Access and ID Tokens. var accessToken = credential.accessToken; var idToken = credential.idToken; // IdP data available in result.additionalUserInfo.profile. // ... }) .catch((error) => { // Handle Errors here. var errorCode = error.code; var errorMessage = error.message; // The email of the user's account used. var email = error.email; // The firebase.auth.AuthCredential type that was used. var credential = error.credential; // ... });
此外,您可以使用 linkWithPopup() 和 linkWithRedirect(),將不同的身分識別提供者連結至現有帳戶。
請注意,Apple 規定您必須先取得使用者的明確同意,才能將他們的 Apple 帳戶連結至其他資料。
舉例來說,如要將 Facebook 帳戶連結至目前的 Firebase 帳戶,請使用您從登入 Facebook 的使用者取得的存取權杖:
Web
import { getAuth, linkWithPopup, FacebookAuthProvider } from "firebase/auth"; const auth = getAuth(); const provider = new FacebookAuthProvider(); provider.addScope('user_birthday'); // Assuming the current user is an Apple user linking a Facebook provider. linkWithPopup(auth.currentUser, provider) .then((result) => { // Facebook credential is linked to the current Apple user. // ... // The user can now sign in to the same account // with either Apple or Facebook. }) .catch((error) => { // Handle error. });
Web
const provider = new firebase.auth.FacebookAuthProvider(); provider.addScope('user_birthday'); // Assuming the current user is an Apple user linking a Facebook provider. firebase.auth().currentUser.linkWithPopup(provider) .then((result) => { // Facebook credential is linked to the current Apple user. // Facebook additional data available in result.additionalUserInfo.profile, // Additional Facebook OAuth access token can also be retrieved. // result.credential.accessToken // The user can now sign in to the same account // with either Apple or Facebook. }) .catch((error) => { // Handle error. });
在 Chrome 擴充功能中透過 Firebase 進行驗證
如果您要建構 Chrome 擴充功能應用程式,請參閱 螢幕外文件指南。
請注意,您仍須向 Apple 驗證自訂網域,做法與預設 firebaseapp.com 網域類似:
http://auth.custom.example.com/.well-known/apple-developer-domain-association.txt
權杖撤銷
根據 App Store 審查指南,Apple 規定應用程式若設有帳戶建立機制,就必須讓使用者能從應用程式內啟動帳戶刪除程序。
如要符合這項規定,請按照下列步驟操作:
請確認您已填寫「使用 Apple 帳戶登入」供應商設定的「服務 ID」和「OAuth 代碼流程設定」部分,如「設定使用 Apple 帳戶登入」一節所述。
由於 Firebase 不會儲存透過「使用 Apple 登入」建立的使用者權杖,因此您必須要求使用者再次登入,才能撤銷權杖並刪除帳戶。
然後從
OAuthCredential取得 Apple OAuth 存取權杖,並使用該權杖呼叫revokeAccessToken(auth, token),撤銷 Apple OAuth 存取權杖。const provider = new OAuthProvider('apple.com'); provider.addScope('email'); provider.addScope('name'); const auth = getAuth(); signInWithPopup(auth, provider).then(result => { // Get the Apple OAuth access token. const credential = OAuthProvider.credentialFromResult(result); const accessToken = credential.accessToken; // Revoke the Apple OAuth access token. revokeAccessToken(auth, accessToken) .then(() => { // Token revoked. // Delete the user account. // ... }) .catch(error => { // An error happened. // ... }); });最後,刪除使用者帳戶 (和所有相關聯的資料)。
進階:在 Node.js 中透過 Firebase 進行驗證
如要在 Node.js 應用程式中向 Firebase 進行驗證,請按照下列步驟操作:
使用者的 Apple 帳戶登入,並取得使用者的 Apple ID 權杖。您可以透過下列方式達成此目的:舉例來說,如果您的 Node.js 應用程式有瀏覽器前端:
在後端產生隨機字串 (即「隨機數」),並計算其 SHA256 雜湊值。隨機數是一次性值,用於驗證後端與 Apple 驗證伺服器之間的單一往返行程。
Web
const crypto = require("crypto"); const string_decoder = require("string_decoder"); // Generate a new random string for each sign-in const generateNonce = (length) => { const decoder = new string_decoder.StringDecoder("ascii"); const buf = Buffer.alloc(length); let nonce = ""; while (nonce.length < length) { crypto.randomFillSync(buf); nonce = decoder.write(buf); } return nonce.slice(0, length); }; const unhashedNonce = generateNonce(10); // SHA256-hashed nonce in hex const hashedNonceHex = crypto.createHash('sha256') .update(unhashedNonce).digest().toString('hex');
Web
const crypto = require("crypto"); const string_decoder = require("string_decoder"); // Generate a new random string for each sign-in const generateNonce = function(length) { const decoder = new string_decoder.StringDecoder("ascii"); const buf = Buffer.alloc(length); var nonce = ""; while (nonce.length < length) { crypto.randomFillSync(buf); nonce = decoder.write(buf); } return nonce.slice(0, length); }; const unhashedNonce = generateNonce(10); // SHA256-hashed nonce in hex const hashedNonceHex = crypto.createHash('sha256') .update(unhashedNonce).digest().toString('hex');
在登入頁面中,於「使用 Apple 帳戶登入」設定中指定雜湊的隨機值:
<script src="https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js"></script> <div id="appleid-signin" data-color="black" data-border="true" data-type="sign in"></div> <script> AppleID.auth.init({ clientId: YOUR_APPLE_CLIENT_ID, scope: 'name email', redirectURI: URL_TO_YOUR_REDIRECT_HANDLER, // See the next step. state: '[STATE]', // Optional value that Apple will send back to you // so you can return users to the same context after // they sign in. nonce: HASHED_NONCE // The hashed nonce you generated in the previous step. }); </script>從伺服器端 POST 的驗證回應中取得 Apple ID 權杖:
app.post('/redirect', (req, res) => { const savedState = req.cookies.__session; const code = req.body.code; const state = req.body.state; const appleIdToken = req.body.id_token; if (savedState !== state || !code) { res.status(403).send('403: Permission denied'); } else { // Sign in with Firebase using appleIdToken. (See next step). } });
另請參閱「設定網頁以使用 Apple 登入」。
取得使用者的 Apple ID 權杖後,請使用該權杖建構 Credential 物件,然後使用憑證登入使用者:
Web
import { getAuth, signInWithCredential, OAuthProvider } from "firebase/auth"; const auth = getAuth(); // Build Firebase credential with the Apple ID token. const provider = new OAuthProvider('apple.com'); const authCredential = provider.credential({ idToken: appleIdToken, rawNonce: unhashedNonce, }); // Sign in with credential form the Apple user. signInWithCredential(auth, authCredential) .then((result) => { // User signed in. }) .catch((error) => { // An error occurred. If error.code == 'auth/missing-or-invalid-nonce', // make sure you're sending the SHA256-hashed nonce as a hex string // with your request to Apple. console.log(error); });
Web
// Build Firebase credential with the Apple ID token. const provider = new firebase.auth.OAuthProvider('apple.com'); const authCredential = provider.credential({ idToken: appleIdToken, rawNonce: unhashedNonce, }); // Sign in with credential form the Apple user. firebase.auth().signInWithCredential(authCredential) .then((result) => { // User signed in. }) .catch((error) => { // An error occurred. If error.code == 'auth/missing-or-invalid-nonce', // make sure you're sending the SHA256-hashed nonce as a hex string // with your request to Apple. console.log(error); });
後續步驟
使用者首次登入後,系統會建立新的使用者帳戶,並連結至使用者登入時使用的憑證 (即使用者名稱和密碼、電話號碼或驗證供應商資訊)。這個新帳戶會儲存在 Firebase 專案中,可用於識別專案中每個應用程式的使用者,無論使用者登入方式為何。
-
在應用程式中,如要瞭解使用者的驗證狀態,建議在
Auth物件上設定觀察器。接著,您就能從User物件取得使用者的基本設定檔資訊。請參閱「管理使用者」。 在 Firebase Realtime Database 和 Cloud Storage 安全規則中,您可以從
auth變數取得已登入使用者的專屬使用者 ID, 並使用該 ID 控制使用者可存取的資料。
您可以將驗證供應商憑證連結至現有使用者帳戶,允許使用者透過多個驗證供應商登入應用程式。
如要登出使用者,請呼叫
signOut:
Web
import { getAuth, signOut } from "firebase/auth"; const auth = getAuth(); signOut(auth).then(() => { // Sign-out successful. }).catch((error) => { // An error happened. });
Web
firebase.auth().signOut().then(() => { // Sign-out successful. }).catch((error) => { // An error happened. });