透過 JavaScript 使用 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 新增至您的專案
  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 處理登入流程

如要建構網頁應用程式,最簡單的方法就是 Firebase 會使用 Apple 帳戶處理整個登入流程 Firebase JavaScript SDK

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

  1. 使用對應的 提供者 ID apple.com

    Web

    import { OAuthProvider } from "firebase/auth";
    
    const provider = new OAuthProvider('apple.com');

    Web

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

    Web

    provider.addScope('email');
    provider.addScope('name');

    Web

    provider.addScope('email');
    provider.addScope('name');

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

  3. 選用:如要以特定語言顯示 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'
    });
  4. 使用 OAuth 提供者物件向 Firebase 進行驗證。你可以 提示使用者透過 彈出式視窗,或重新導向至登入頁面。重新導向方法是 在行動裝置上顯示。

    • 如要透過彈出式視窗登入,請呼叫 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():

    使用 signInWithRedirectlinkWithRedirectreauthenticateWithRedirect 時,請遵循最佳做法

    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

撤銷權杖

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

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

  1. 確認您已填寫服務 IDOAuth 程式碼流程 「使用 Apple「使用 Apple 帳戶登入」設定」部分, 設定「使用 Apple 帳戶登入」一文所述的步驟 專區。

  2. 由於 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.
          // ...
        });
    });
    
  3. 最後,刪除使用者帳戶 (和 相關資料)。

進階:使用 Node.js 以 Firebase 進行驗證

如要在 Node.js 應用程式中使用 Firebase 進行驗證:

  1. 透過 Apple 帳戶登入使用者,並取得使用者的 Apple ID 產生下一個符記您可以透過幾種方式達成此目的。舉例來說,如果您的 Node.js 應用程式的瀏覽器前端:

    1. 在後端產生隨機字串 (「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');
    2. 在登入頁面上,在登入工具中,指定經過雜湊處理的 Nonce 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>
      
    3. 從 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 登入的網頁

  2. 取得使用者的 Apple ID 權杖後,請使用該權杖建立憑證 物件,然後以憑證登入使用者:

    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 即時資料庫和 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.
});