您可以使用 Firebase SDK 執行端對端 OAuth 2.0 登入流程,讓使用者透過 Apple ID 向 Firebase 驗證身分。
事前準備
如要讓使用者透過 Apple 登入,請先在 Apple 開發人員網站上設定「使用 Apple 帳戶登入」,然後在 Firebase 專案中啟用 Apple 做為登入供應商。
加入 Apple Developer Program
只有 Apple Developer Program 成員可以設定「使用 Apple 帳戶登入」功能。
設定「使用 Apple 帳戶登入」
在 Apple 開發人員網站上執行下列操作:
-
按照「 設定網頁版使用 Apple 帳戶登入」第一節的說明,將網站與應用程式建立關聯。系統提示時,請將下列網址註冊為回傳網址:
https://YOUR_FIREBASE_PROJECT_ID.firebaseapp.com/__/auth/handler
如要查看 Firebase 專案 ID,請前往 Firebase 控制台的「設定」
>「一般」分頁標籤。 完成後,請記下新的服務 ID,下個部分會用到。
- 建立「使用 Apple 登入」私密金鑰。您會在下一節中用到新的私密金鑰和金鑰 ID。
-
如果您使用任何會傳送電子郵件給使用者的 Firebase Authentication 功能,包括電子郵件連結登入、電子郵件地址驗證、帳戶變更撤銷等,請 設定 Apple 私人電子郵件轉送服務,並註冊
noreply@YOUR_FIREBASE_PROJECT_ID.firebaseapp.com(或自訂電子郵件範本網域),這樣 Apple 就能將 Firebase Authentication 傳送的電子郵件轉送至匿名 Apple 電子郵件地址。
啟用 Apple 做為登入供應商
- 將 Firebase 新增至專案。
- 在 Firebase 控制台中,依序前往「安全性」>「驗證」。
- 在「登入方式」分頁中,啟用「Apple」登入供應商。 指定您在前一節建立的服務 ID。此外,在「OAuth code flow configuration」(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 驗證。
如要使用 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 會要求電子郵件和名稱範圍。如果將這項設定變更為「Multiple accounts per email address」(每個電子郵件地址有多個帳戶),除非您指定範圍,否則 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取得該名稱。不過,如果您先前使用 Apple 登入應用程式,但沒有使用 Firebase,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 帳戶登入」供應商設定中的「Services ID」和「OAuth code flow configuration」部分,詳情請參閱「設定使用 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 應用程式有瀏覽器前端:
在後端產生隨機字串 (「Nonce」),並計算其 SHA256 雜湊值。Nonce 是單次使用的值,用於驗證後端與 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>從 POSTed 驗證回應伺服器端取得 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, 並用來控管使用者可存取的資料。
您可以將驗證供應商憑證連結至現有使用者帳戶,允許使用者透過多個驗證供應商登入應用程式。
如要登出使用者,請呼叫
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. });