JavaScript による Apple を使用した認証

Firebase SDK を使用してエンドツーエンドの OAuth 2.0 ログインフローを実行すると、ユーザーが Firebase での認証に Apple ID を使用できるようになります。

始める前に

ユーザーが Apple を使用してログインできるようにするには、まず Apple のデベロッパー サイトで「Apple でサインイン」を構成してから、Firebase プロジェクトのログイン プロバイダとして Apple を有効にします。

Apple Developer Program に参加する

「Apple でサインイン」は Apple Developer Program のメンバーのみが構成できます。

「Apple でサインイン」を構成する

Apple Developer サイトでの手順は次のとおりです。

  1. Web 向けに「Apple でサインイン」を設定するの最初のセクションの説明に従って、ウェブサイトをアプリに関連付けます。プロンプトが表示されたら、次の URL を戻り先 URL として登録します。

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

    Firebase プロジェクト ID は、Firebase コンソールの [プロジェクトの設定] ページで確認できます。

    作業が完了したら、新しいサービス ID をメモしておきます。この ID は次のセクションで必要になります。

  2. 「Apple でサインイン」の秘密鍵を作成します。新しい秘密鍵と鍵 ID は、次のセクションで必要になります。
  3. メールリンク ログイン、メールアドレスの確認、アカウントの変更の取り消しなど、ユーザーにメールを送信する Firebase Authentication の機能を使用する場合は、Apple のプライベート メール リレーサービスを構成し、noreply@YOUR_FIREBASE_PROJECT_ID.firebaseapp.com(またはカスタマイズしたメール テンプレート ドメイン)を登録します。これにより Apple は、Firebase Authentication によって送信されたメールを、匿名化された Apple のメールアドレスに転送できます。

Apple をログイン プロバイダとして有効にする

  1. プロジェクトに Firebase を追加します。
  2. Firebase コンソールで [Authentication] セクションを開きます。[Sign-in method] タブで、[Apple] プロバイダを有効にします。前のセクションで作成したサービス ID を指定します。また、[OAuth コードフローの構成] セクションで、Apple チーム ID、前のセクションで作成した秘密鍵と鍵 ID を指定します。

Apple の匿名化データの要件を遵守する

「Apple でサインイン」には、ユーザーがログイン時に、メールアドレスを含む自分のデータを匿名化できるオプションがあります。このオプションを選択したユーザーには、ドメイン privaterelay.appleid.com のメールアドレスが作成されます。アプリで「Apple でサインイン」を使用する場合は、これらの匿名化された Apple ID に関して、Apple が定めるデベロッパー ポリシーと利用規約を遵守する必要があります。

これには、本人を直接特定できる個人情報を、匿名化された Apple ID に関連付ける前に、必要なユーザーの同意を得ることも含まれます。Firebase Authentication を使用する場合、この関連付けには、次のアクションが該当することがあります。

  • 匿名化された Apple ID にメールアドレスをリンク(またはその逆方向にリンク)する。
  • 匿名化された Apple ID に電話番号をリンク(またはその逆方向にリンク)する。
  • 匿名化された Apple ID に匿名ではないソーシャル認証情報(Facebook、Google など)をリンク(またはその逆方向にリンク)する。

上記のリストはすべてを網羅しているわけではありません。アプリが Apple の要件を満たしていることを確認するには、デベロッパー アカウントの [Membership] セクションにある Apple Developer Program License Agreement をご覧ください。

Firebase SDK を使用したログインフローの処理

ウェブアプリをビルドする場合、Apple アカウントを使用して Firebase でユーザーを認証する最も簡単な方法は、Firebase JavaScript SDK でログインフロー全体を処理することです。

Firebase JavaScript SDK でログインフローを処理する手順は次のとおりです。

  1. 対応するプロバイダ ID apple.com を使用して、OAuthProvider のインスタンスを作成します。

    ウェブ向けのモジュラー API

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

    ウェブ向けの名前空間付き API

    var provider = new firebase.auth.OAuthProvider('apple.com');
  2. 省略可: 認証プロバイダにリクエストする、デフォルトを超える追加の OAuth 2.0 スコープを指定します。

    ウェブ向けのモジュラー API

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

    ウェブ向けの名前空間付き API

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

    デフォルトでは、[1 つのメールアドレスにつき 1 つのアカウント] が有効である場合、Firebase はメールアドレスと名前のスコープをリクエストします。この設定を [1 つのメールアドレスにつき複数のアカウント] に変更すると、Firebase は Apple にスコープをリクエストしません(指定した場合を除きます)。

  3. 省略可: Apple のログイン画面を英語以外の言語で表示する場合は、locale パラメータを設定します。サポートされる言語 / 地域については、「Apple でサインイン」に関するドキュメントをご覧ください。

    ウェブ向けのモジュラー API

    provider.setCustomParameters({
      // Localize the Apple authentication screen in French.
      locale: 'fr'
    });

    ウェブ向けの名前空間付き API

    provider.setCustomParameters({
      // Localize the Apple authentication screen in French.
      locale: 'fr'
    });
  4. OAuth プロバイダ オブジェクトを使用して Firebase での認証を行います。ユーザーに Apple アカウントでログインするよう促すために、ポップアップ ウィンドウを表示するか、ログインページにリダイレクトします。モバイル デバイスではリダイレクトすることをおすすめします。

    • ポップアップ ウィンドウでログインする場合は、signInWithPopup() を呼び出します。

      ウェブ モジュラー API

      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);
      
          // ...
        });

      ウェブ向けの名前空間付き API

      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 を使用する場合は、ベスト プラクティスに従ってください。

    ウェブ向けのモジュラー API

    import { getAuth, signInWithRedirect } from "firebase/auth";
    
    const auth = getAuth();
    signInWithRedirect(auth, provider);

    ウェブ向けの名前空間付き API

    firebase.auth().signInWithRedirect(provider);

    ユーザーがログインを完了してページに戻ったら、getRedirectResult() を呼び出してログイン結果を取得できます。

    ウェブ向けのモジュラー API

    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);
    
        // ...
      });

    名前空間が指定されたウェブ API

    // 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 では写真の URL が提供されません。

    また、ユーザーがアプリとメールの共有を行わない場合、Apple はそのユーザーに固有のメールアドレス(xyz@privaterelay.appleid.com の形式)をプロビジョニングし、これをアプリと共有します。プライベート メール リレー サービスを構成した場合、Apple は、匿名化されたアドレスに送信されたメールを、ユーザーの実際のメールアドレスに転送します。

    Apple が表示名などのユーザー情報をアプリと共有するのは、ユーザーの初回ログイン時のみです。通常、ユーザーが初めて Apple でログインしたときに Firebase で表示名が保存されます。この情報は firebase.auth().currentUser.displayName で取得できます。ただし、以前に Apple を使用してアプリへのユーザーのログインを行った際に Firebase を使用していなかった場合、Apple はユーザーの表示名を Firebase に渡しません。

再認証とアカウントのリンク

同じパターンを reauthenticateWithPopup()reauthenticateWithRedirect() でも使用できます。これらは、ログインしてから短時間のうちに行うべき機密性の高い操作のために、最新の認証情報を取得するのに使用できます。

ウェブ向けのモジュラー API

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);

    // ...
  });

ウェブ向けの名前空間付き API

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() を使用して、複数の ID プロバイダを既存のアカウントにリンクすることができます。

Apple は、Apple アカウントを他のデータにリンクする前にユーザーから明示的な同意を得ることを要件としています。

たとえば、Facebook アカウントを現在の Firebase アカウントにリンクするには、Facebook へのユーザーのログイン時に取得したアクセス トークンを使用します。

ウェブ向けのモジュラー API

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.
    });

名前空間が指定されたウェブ API

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 拡張機能アプリを作成する場合は、Chrome 拡張機能 ID を追加する必要があります。

  1. Firebase コンソールでプロジェクトを開きます。
  2. [Authentication] セクションで、[Sign-in method] ページを開きます。
  3. 承認済みドメインのリストに、次のような URI を追加します。
    chrome-extension://CHROME_EXTENSION_ID

Chrome 拡張機能では HTTP リダイレクトを使用できないため、Chrome 拡張機能ではポップアップ オペレーション(signInWithPopuplinkWithPopupreauthenticateWithPopup)のみを使用できます。認証ポップアップはブラウザのアクション ポップアップをキャンセルするため、ブラウザのアクション ポップアップではなくバックグラウンド ページ スクリプトからこれらのメソッドを呼び出す必要があります。ポップアップ メソッドは、Manifest V2 を使用する拡張機能でのみ使用できます。新しい Manifest V3 では、Service Worker 形式のバックグラウンド スクリプトのみが許可されます。そのため、ポップアップ オペレーションは一切実行できません。

Chrome 拡張機能のマニフェスト ファイルで、https://apis.google.com URL を content_security_policy 許可リストに必ず追加してください。

デフォルトの firebaseapp.com ドメインと同様に、カスタム ドメインも Apple で確認する必要があります。

http://auth.custom.example.com/.well-known/apple-developer-domain-association.txt

高度: Node.js 内で Firebase で認証する

Node.js アプリケーションで Firebase により認証する手順は、次のとおりです。

  1. Apple アカウントでユーザーをログインさせ、ユーザーの Apple ID トークンを取得します。これには複数の方法があります。たとえば、Node.js アプリにブラウザのフロントエンドがある場合は、次のようにします。

    1. バックエンドで、ランダムな文字列(「ノンス」)を生成し、その SHA256 ハッシュを計算します。ノンスとは、バックエンドと Apple の認証サーバー間の 1 回のラウンド トリップを検証するために使用する 1 回限りの値です。

      ウェブ向けのモジュラー API

      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');

      ウェブ向けの名前空間付き API

      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. ログインページで、「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. 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 でサインイン」の構成もご覧ください。

  2. ユーザーの Apple ID トークンを取得した後、そのトークンを使用して認証情報オブジェクトを作成してから、その認証情報でユーザーをログインさせます。

    ウェブ向けのモジュラー API

    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);
      });

    ウェブ向けの名前空間付き API

    // 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 のセキュリティ ルールでは、ログイン済みユーザーの一意のユーザー ID を auth 変数から取得し、それを使用して、ユーザーがアクセスできるデータを管理できます。

既存のユーザー アカウントに認証プロバイダの認証情報をリンクすることで、ユーザーは複数の認証プロバイダを使用してアプリにログインできるようになります。

ユーザーをログアウトするには、signOut を呼び出します。

ウェブ モジュラー API

import { getAuth, signOut } from "firebase/auth";

const auth = getAuth();
signOut(auth).then(() => {
  // Sign-out successful.
}).catch((error) => {
  // An error happened.
});

名前空間が指定されたウェブ API

firebase.auth().signOut().then(() => {
  // Sign-out successful.
}).catch((error) => {
  // An error happened.
});