Identity Platform と Google の ID を使用して Firestore にアクセスするユーザーを認証する


このドキュメントでは、Identity Platform をユーザーの Identity and Access Management プラットフォームとして使用して、ユーザー単位のアクセス制御を Firestore データベースに設定する方法を紹介します。Identity Platform では、アプリケーションに認証レイヤを追加して、ユーザーの認証情報の保護と管理を行うことができます。Firestore は、柔軟な NoSQL ドキュメント指向データベースです。Firestore セキュリティ ルールというルール言語を使用してこのデータへのアクセスを制御するため、サーバー側で認証コードを記述する必要はありません。

このドキュメントは、Firestore で Firestore セキュリティ ルールを使用し、Google などの外部ログイン プロバイダを使用して Identity Platform でユーザーを認証するデベロッパーとセキュリティの専門家を対象としています。このドキュメントのコードでは、Identity Platform と Firestore を使用する 2 つの方法を説明します。

  • REST API 呼び出し。JavaScript を使用して Identity Platform API と Firestore API を呼び出します。このアプローチでは、ウェブアプリで Identity Platform に対するリクエストを作成する方法を完全に制御できます。
  • Identity Platform Client SDK。Identity Platform Client SDK と Firestore SDK を使用して、Identity Platform へのログイン プロセスの管理と Firestore に対するクエリの実行を行います。この SDK では、Identity Platform REST API を介して JavaScript ラッパー関数が提供されます。これにより、手動で HTTP リクエストを作成する代わりに、JavaScript 関数とオブジェクトを使用して Identity Platform を呼び出すことができます。

Identity Platform Client SDK と Firebase Client SDK は同じ SDK を共有し、この SDK は、Identity Platform のすべての機能をサポートしています。SDK は下位互換性を維持するために、Firebase のブランディングを保持しています。

アーキテクチャ

次の図は、このドキュメントで説明する論理アーキテクチャを示しています。

論理アーキテクチャを示す図。

このドキュメントでは、Identity Platform と Firestore に加えて、次のコンポーネントを使用して説明します。

  • ウェブアプリ: ユーザーが Google の ID を使用して Identity Platform にログインするために使用するアプリ。このアプリは、ログインするユーザーに関する情報を Firestore に問い合わせます。
  • Google ログイン: この例で使用する ID プロバイダ。
  • 認証ハンドラ: Google からのレスポンスを取得し、Identity Platform でログインを実行して、その結果をウェブアプリに返してログインを完了するサービス エンドポイント。

目標

  • Google Cloud プロジェクトに Identity Platform を設定する。
  • Google を Identity Platform のログイン プロバイダとして追加する。
  • Firestore セキュリティ ルールを使用して Firestore データベースへのアクセスを制御する。
  • Identity Platform API と Identity Platform Client SDK でユーザーがウェブアプリにログインする。
  • Firestore REST API と Firestore JavaScript Client SDK を使用して、クライアント側ウェブアプリから Firestore に安全にアクセスする。

費用

このドキュメントでは、Google Cloud の次の課金対象のコンポーネントを使用します。

料金計算ツールを使うと、予想使用量に基づいて費用の見積もりを生成できます。 新しい Google Cloud ユーザーは無料トライアルをご利用いただける場合があります。

始める前に

  1. Google Cloud Console の [プロジェクト セレクタ] ページで、Google Cloud プロジェクトを選択または作成します。

    プロジェクト セレクタに移動

    既存のプロジェクトを選択する場合は、次の条件を満たすプロジェクトを選択する必要があります。

    • Datastore が有効になっていない。
    • App Engine が有効になっていない。
    • プロジェクトに、既存のセキュリティ ルールを使用した Firestore データベースが含まれていない。既存のルールはすべて、このドキュメントで説明するルールで上書きします。
  2. Google Cloud プロジェクトで課金が有効になっていることを確認します

  3. Identity Platform を有効にします。
    1. Google Cloud コンソールで Identity Platform の Marketplace ページに移動します。

      Identity Platform の [Marketplace] ページに移動

    2. [ID プラットフォームを有効化] をクリックし、オペレーションが完了するまで待ちます。
  4. Firestore を有効にします。
    1. Google Cloud コンソールで左側にあるメニューを開き、[Firestore] を選択します。
    2. Firestore モードを選択します。[ネイティブ モードを選択] をクリックします。
    3. データの保存場所の選択自身のロケーションに最も近いリージョンを選択してください。[データベースを作成] をクリックします。

このドキュメントに記載されているタスクの完了後、作成したリソースを削除すると、それ以上の請求は発生しません。詳細については、クリーンアップをご覧ください。

Identity Platform の構成

Identity Platform でユーザー認証を有効にするには、ID プロバイダを追加する必要があります。

Identity Platform で Google プロバイダを構成する前に、Google OAuth 同意画面を構成する必要があります。この同意画面は、ウェブアプリへの初回ログイン時に表示されます。OAuth 同意画面の構成では、アプリの名前、アプリのロゴ、サポートのメールアドレスなどの属性を設定できます。

  1. Google Cloud コンソールで、[ID プロバイダ] ページに移動します。

    [ID プロバイダ] ページに移動

  2. [プロバイダを追加] をクリックします。
  3. [Google] を選択し、[新しい ID プロバイダ] ページで [API とサービス] リンクをクリックします。[認証情報] ページが新しいタブで開きます。[認証情報] ページから [新しい ID プロバイダ] ページにコピーする必要があるため、前のタブを閉じないでください。
  4. [認証情報] ページで、[Web client (auto created by Google Service)] をクリックします。
  5. [クライアント ID] の値をコピーし、[新しい ID プロバイダ] ページが表示されているタブに移動して、[Web Client ID] フィールドに値を貼り付けます。この手順を繰り返して、[クライアント シークレット] の値を [Web Client Secret] フィールドにコピーします。
  6. [新しい ID プロバイダ] ページで、[保存] をクリックします。
  7. [認証情報] ページに戻り、[OAuth 同意画面] をクリックします。
  8. [外部] を選択して、[作成] をクリックします。
  9. [OAuth 同意画面] ページで、次の情報を追加します。
    • アプリケーション名: Identity Platform Tutorial
    • サポートメール: プルダウン リストからメールアドレスを選択します。
  10. [保存] をクリックします。
  11. [アプリの登録の編集] ページで、以下の情報を追加します。
    • アプリ名: Identity Platform Tutorial
    • ユーザー サポートメール: プルダウン リストからメールアドレスを選択します。
    • メールアドレス: メールアドレスを入力します。
  12. [保存して次へ] をクリックします。
  13. [スコープ] と [省略可能な情報] ページで、[保存して次へ] をクリックします。
  14. [概要] ページで、[ダッシュボードに戻る] をクリックします。

Firestore の構成

この段階で、作成した新しい Firestore データベースは空です。また、この新しい Firestore データベースには、誰でもデータベースに対して読み取りオペレーションを実行できるデフォルトのセキュリティ ルール一式が適用されています。このデフォルトのルールでは、データベースに対する書き込みオペレーションは許可されません。次の手順では、データベースにデータを追加し、セキュリティ ルールを更新して、読み取り(クエリ)リクエストを許可されたユーザーのみに制限します。

Firestore でテストデータを作成する

この手順では、ウェブアプリから Firestore コレクションに対してクエリを実行して、セキュリティ ルールをテストします。

まず、Firestore のセキュリティ ルールのテストに使えるドキュメントをいくつか作成します。コレクション名とフィールド名の大文字と小文字は区別されます。コレクションとフィールドに小文字の名前を使用することで、ウェブアプリが Firestore にクエリを送信する際のエラーを防止できます。

  1. Google Cloud コンソールで [Firestore] ページに移動します。
  2. [コレクションを開始] をクリックします。
  3. [コレクション ID] フィールドに「customers」と入力します。
  4. 次の情報を含むドキュメントを作成します。

    ドキュメント ID: bob@example.com

    フィールド名 フィールド タイプ フィールド値
    name 文字列 Bob
    company 文字列
    
    ExampleOrganization
  5. [Save and add another] をクリックします。

  6. [フィールド値を消去] をクリックします。

  7. 次の情報を入力します。

  8. ドキュメント ID: メールアドレス

    フィールド名 フィールド タイプ フィールド値
    name 文字列 担当者名
    company 文字列 会社名
  9. [保存] をクリックします。

Firestore セキュリティ ルールを作成する

Firestore のセキュリティ ルールは、ユーザー認証メタデータ、受信クエリからのデータ、データベース内の既存のデータを使用して評価できます。

この手順では、ログイン ユーザーのメールアドレスとプロバイダ名に基づいた Firestore のセキュリティ ルールを作成します。

Firebase コンソールを使用して、Firestore セキュリティ ルールを管理します。

  1. Firebase コンソールを開き、プロジェクトをクリックします。
  2. 画面左側にある [Firestore] をクリックします。
  3. [Firestore] ページで [ルール] タブをクリックします。
  4. セキュリティ ルールが適用された Firestore データベースがある既存のプロジェクトを使用する場合で、このドキュメントの手順を完了したときにプロジェクトのクリーンアップを計画している場合は、既存のセキュリティ ルールをメモしてください。
  5. 既存のルールを以下のルールに置き換えます。

    rules_version = '2';
    service cloud.firestore {
     match /databases/{database}/documents {
      match /customers/{customerID} {
      allow read:
       if request.auth.uid != null
         && request.auth.token.firebase.sign_in_provider == "google.com"
         && request.auth.token.email == customerID
        }
      }
    }
    

    このルールは、customers コレクションの読み取り専用権限を付与します。このルールは、Identity Platform で構成した Google ログイン プロバイダを介してログインされたかどうかを検証します。また、ユーザーのメールアドレスと一致する顧客 ID を持つドキュメントのみを取得できるようにします。

  6. [Publish] をクリックします。

テスト環境の構成

Firestore セキュリティ ルールをテストするために、ユーザーにログインを要求するウェブアプリをまず作成します。このウェブアプリは GitHub から取得可能であり、アプリケーションのテストが可能な Cloud Shell 環境にダウンロードできます。ユーザーがログインすると、ウェブアプリが Firestore からドキュメントを読み取り、そのドキュメントの内容を表示します。

ウェブアプリを構成する

  1. Identity Platform の [プロバイダ] ページに移動します。

    Identity Platform の [プロバイダ] ページに移動

  2. ページ右側にある [アプリケーション設定の詳細] をクリックします。
  3. apiKeyauthDomain の横にある値をクリップボードにコピーし、[閉じる] をクリックします。
  4. ページの上部にある [Google Cloud] をクリックし、[プロジェクト情報] カードからプロジェクト ID をコピーします。
  5. [Cloud Shell で開く] をクリックして Cloud Shell を開き、GitHub リポジトリのクローンを作成し、config.js ファイルを開きます。[Cloud Shell で開く] ダイアログが表示されたら、[確認] をクリックします。

    Cloud Shell で開く

  6. config.js ファイルで、プレースホルダ [API_KEY][AUTH_DOMAIN][PROJECT_ID] を前の手順でコピーした値に置き換えます。これらの値は、Identity Platform にリクエストを送信するときに作成するメッセージの URL と本文に挿入されます。

カスタム認証ハンドラを登録する

ユーザーがウェブアプリにアクセスすると、ID プロバイダとして Google を使用してログインするようにリダイレクトされます。アーキテクチャの図に示すように、ユーザーが Google へのログインに成功すると、Google は、ユーザーのトークンとともにリダイレクト(302)レスポンスを認証ハンドラに返します。OAuth 2.0 では、プロバイダがトークンを不明な宛先に送信しないように、プロバイダの構成にすべてのリダイレクト URL を事前に登録する必要があります。OAuth 2.0 ウェブサイトのリダイレクト URL の登録ページでは、この制限の理由について説明されています。

この手順では、このドキュメントで使用されている認証ハンドラを信頼するように、承認済みリダイレクト URL リストを更新します。

この URL リストは Google OAuth 2.0 クライアント構成の一部です。これは、Identity Platform の構成時にクライアント ID とクライアント シークレットをコピーしたページです。

このアーキテクチャでは、2 種類の認証ハンドラを使用します。

  • Identity Platform でホストされる認証ハンドラ。
  • ウェブアプリでホストされるカスタム認証ハンドラ。

Identity Platform でホストされる認証ハンドラには、Google が管理する次のエンドポイントからアクセスできます。https://[YOUR_PROJECT_ID].firebaseapp.com/__/auth/handler

このハンドラを使用するために、Google OAuth 2.0 のクライアント設定で承認済み URL リストを更新する必要はありません。このドキュメントで先に Identity Platform を有効しておくと、URL が承認済み URL リストに自動的に追加されます。

Identity Platform Client SDK を使用する場合、SDK はこの組み込みの認証ハンドラを使用します。Identity Platform の認証ハンドラでは、SDK が状態オブジェクトとハンドラを交換するため、ウェブアプリでこの SDK を使用する必要があります。たとえば SDK は、ユーザーが Identity Platform に正常にログインした後、ユーザーをリダイレクトする場所をハンドラに通知します。

ウェブアプリでホストされるカスタム認証ハンドラの場合で、JavaScript から直接 Identity Platform REST API を使用する場合は、SDK ではなく、独自の認証ハンドラを実装してホストすることをおすすめします。

このドキュメントでは、Google からユーザー トークンを受け取ったときに Identity Platform のログイン プロセスを管理するサンプル認証ハンドラについて説明しています。カスタム ハンドラの URL を、Google OAuth 2.0 クライアント設定の承認済み URL リストに追加する必要があります。

このドキュメントでは、Cloud Shell からウェブアプリを実行します。ウェブアプリを起動したら、Cloud Shell インスタンスのホスト名を見つけて、プロバイダの構成を必要に応じて更新します。

  1. Cloud Shell からウェブアプリを実行します。

    npm install
    node app.js
    
  2. 次の出力が表示されるまで待ちます。Example app listening on port 8080!

  3. [ウェブでプレビュー] アイコンをクリックし、[ポート 8080 でプレビュー] をクリックします。新しいタブにウェブページが表示されるまで待ち、[Auth handler URL(for Google OAuth 2.0 Client)] に値をコピーします。

  4. [認証情報] ページに移動します。
    [認証情報] ページに移動

  5. [認証情報] ページで、[Web client (auto created by Google Service)] をクリックします。

  6. [承認済みのリダイレクト URI] ページで [URI を追加] をクリックし、先ほどコピーした URL を貼り付けます。

  7. [保存] をクリックして、ウェブアプリを実行したままにします。

ウェブアプリのドメインを承認する

Identity Platform 認証ハンドラを使用すると、ハンドラはユーザー情報とトークンとともにユーザーをウェブアプリにリダイレクトします。未承認のドメインに情報が送信されることを防止するために、ウェブアプリが実行されるドメインを承認する必要があります。

  1. ウェブアプリのデフォルトのウェブページに戻り、[Hostname(for Identity Platform Authorized Domains)] の値をコピーします。
  2. Identity Platform の [設定] ページに移動します。

    Identity Platform の [設定] ページに移動

  3. [セキュリティ] タブをクリックし、[ドメインの追加] をクリックします。
  4. コピーしたドメインを貼り付け、[追加] をクリックしてから [保存] をクリックします。
  5. Cloud Shell でウェブアプリを実行したままにします。これは、次のタスクで必要になります。

Google の ID で Identity Platform にログインする

以下の図は、このガイドの冒頭にあるアーキテクチャの概要図を拡張したものです。この図は、このドキュメントで説明する認証プロセスの詳細について、イベントの時系列の流れに沿って説明したものです。このイベントは、ユーザーによるログインボタンのクリックから始まり、ウェブアプリがユーザー ID を使用して Firestore のデータを取得する時点で完了します。

アーキテクチャの概要

  1. ウェブアプリのユーザーが、ウェブアプリで [Google でログイン] をクリックします。
  2. ウェブアプリが、選択された ID プロバイダ(この場合は Google)のログイン URL を Identity Platform に問い合わせます。
  3. ウェブアプリが、認証ハンドラを指すコールバック URL とともに、ユーザーを ID プロバイダのログインページにリダイレクトします。
  4. プロバイダのログインページで、ユーザーが認証情報を入力し、ウェブアプリによって要求される承認に同意します。
  5. ユーザーがログインに成功したら、プロバイダがトークンを生成し、指定されたコールバック URL にリダイレクトを送信します。
  6. 認証ハンドラが、Google が発行したトークンを受け取り、それを Identity Platform に送信してユーザーをログインさせます。Identity Platform がトークンを検証し、ユーザーをログインさせ、Identity Platform が発行した ID トークンと更新トークンをユーザーの情報とともに返します。Identity Platform がユーザーを初めてログインさせる場合、照合用のユーザー プロフィールがデータベース内に作成されます。Google が提供するアカウント情報は、ユーザーのプロフィールを入力するために使用されます。
  7. ユーザーが Identity Platform にログインすると、ハンドラは、ユーザーを Identity Platform から取得した新しいトークンとともにウェブアプリにリダイレクトします。
  8. Firestore にリクエストを送信するために、ウェブアプリがすべての Firestore リクエストにユーザーの ID トークンを添付します。Firestore のセキュリティ ルールが、Firestore で ID トークンのないリクエストを匿名のリクエストとして扱い、拒否することを定めます。
  9. Identity Platform により発行される ID トークンは、1 時間で有効期限切れになります。ID トークンの有効期限が切れた場合、ウェブアプリは、キャッシュに保存された更新トークンを使用して Identity Platform から新しい ID トークンを取得します。

サンプルのウェブアプリで、Identity Platform と Firestore とやり取りを行うための以下の 2 つの方法について説明します。

1 つ目の方法は Identity Platform REST API を使用する方法です。

  • この手法では、JavaScript で Identity Platform REST API を呼び出すカスタムコードを使用します。
  • API 呼び出しは、site/identity-platform-auth-helper.js ファイルに実装されています。認証ハンドラは、views/auth-handler.ejs ファイルに実装されています。
  • ヘルパーとハンドラは状態オブジェクトを交換して、ログインに成功した後にユーザーがウェブアプリに戻れるようにします。

2 つ目の方法は Identity Platform Client SDK を使用する方法です。

  • この方法では、SDK によりログイン プロセスが処理されます。
  • この SDK は、必要なすべての API 呼び出しを実装し、一連の関数をデベロッパーに公開して、開始するログインフローを制御します。

Identity Platform REST API を使用してログインする

ID プロバイダとして Google を使用するログインフローを制御する主要な API 呼び出しは 2 つあります。

プロバイダとして Google を使用するログインフローを制御する 2 つの主要な API 呼び出し

  • プロバイダの URL と識別子を取得します。accounts.createAuthUri メソッドは、指定された ID プロバイダの承認 URL を返します。その後、ウェブアプリが返された認証 URL に移動し、選択された ID プロバイダ(Google など)によるログイン プロセスを開始します。

    次のコード スニペットは、この API を呼び出す方法を示しています。

    IdentityPlatformAuthHelper.prototype.createAuthUri = function(providerId, tenantId) {
      // https://cloud.google.com/identity-platform/docs/reference/rest/v1/accounts/createAuthUri
      const createAuthUriUrl = `${this.identityPlatformBaseUrl}/accounts:createAuthUri?key=${config.apiKey}`;
      const request = {
        'providerId' : providerId,
        'tenantId' : tenantId,
        'continueUri' : this.authHandlerUrl,
      };
    
      return fetch(
          createAuthUriUrl,
          {
            contentType: 'application/json',
            method: 'POST',
            body: JSON.stringify(request)
          }
        )
      .then(response => response.json())
      .then(data => {
        return {
          "authUri" : data.authUri,
          "sessionId" : data.sessionId
        };
      })
      .catch(error => {
        console.error(error);
      });
    };
  • Google 発行のトークンを使用して Identity Platform にログインします。accounts.signInWithIdp メソッドでは、ID プロバイダからの承認レスポンスを使用して、Identity Platform にログインします。API は、Identity Platform によって発行された新しいトークンでこのリクエストに応答します。ウェブアプリは、ID プロバイダから承認レスポンスを受信すると、この API を呼び出します。次のコード スニペットは、この API を呼び出す方法を示しています。

    IdentityPlatformAuthHelper.prototype.signInWithIdp = function(data) {
      authState = this.getAuthState();
      this.authHandlerUrl = authState.authHandlerUrl;
    
      // https://cloud.google.com/identity-platform/docs/reference/rest/v1/accounts/signInWithIdp
      const signInWithIdpUrl = `${this.identityPlatformBaseUrl}/accounts:signInWithIdp?key=${config.apiKey}`;
    
      const request = {
          'requestUri' : this.authHandlerUrl,
          'sessionId' : authState.sessionId,
          'returnRefreshToken' : true,
          'returnSecureToken' : true,
          'tenantId' : authState.tenantId
        };
    
      if (authState.providerId == 'google.com' || authState.providerId.startsWith('saml.')) {
        request.postBody = `${data}&providerId=${authState.providerId}`;
      } else {
        throw new Error('This sample script only supports the google.com and SAML providers for Identity Platform');
      }
    
      fetch(
          signInWithIdpUrl,
          {
            contentType: 'application/json',
            method: 'POST',
            body: JSON.stringify(request)
          }
        )
      .then(response => response.json())
      .then(data => {
        this.user = data;
        this.signedInHandler(this.user);
      })
      .catch(error => {
        console.error(error);
      });
    }

    postBody フィールドの値の形式は、選択された ID プロバイダと使用する認証プロトコルによって異なります。このコードは、OpenID Connect(OIDC)トークンを使用して Google ID プロバイダを処理し、提供された SAML レスポンスを使用して SAML ベースの ID プロバイダを処理します。OAuth 2.0 アクセス トークンや OAuth 1.0 アクセス トークンなど、他の種類の認証トークンを使用している場合は、プロバイダの API ドキュメントを確認してください。

ユーザーがウェブアプリにログインしたら、ウェブアプリから Firestore にクエリを送信できます。

ウェブアプリが Firestore にクエリを送信するときのイベントのフロー

このコードが Firestore REST API へのリクエストをトリガーする前に、Identity Platform により発行され署名された ID トークンをリクエストに追加します。次のコード スニペットは、リクエストの作成方法を示しています。

function showCustomerInformation(userEmail) {
  $('#customer-information').show();
  $('#output').empty();

  const idTokenPromise = authHelper.getIdToken();
  const firestoreEndpoint = 'https://firestore.googleapis.com/v1';
  const defaultDbPath = `projects/${config.projectId}/databases/(default)/documents`;
  const collectionId = 'customers';

  // Call Firestore via its REST API and authenticate with the user's ID token
  idTokenPromise
  .then(idToken => {
    console.log(`JWT Token: ${idToken}`);
    return fetch(
      `${firestoreEndpoint}/${defaultDbPath}/${collectionId}/${userEmail}`,
      {
        headers: {
          'Authorization': 'Bearer ' + idToken
        },
        contentType: 'application/json',
        method: 'GET'
      })
  })
  .then(response => response.json())
  .then(data => {
      if (data.error) {
        throw data.error.message;
      }
      var fields = data.fields;
      $('#output').append($('<p>').text(`Id: ${userEmail}`));
      $('#output').append($('<p>').text(`Name: ${fields.name.stringValue}`));
      $('#output').append($('<p>').text(`Company: ${fields.company.stringValue}`));
      $('#output').append($('<p>').text(`Doc path: ${data.name}`));
      $('#output').append($('<p>').text(`Doc URL: ${firestoreEndpoint}/${data.name}`));
  })
  .catch(error => {
    console.error(error);
    $('#output').text("Error: " + JSON.stringify(error));
  });
}

IdentityPlatformAuthHelper.getIdToken() 関数は、ブラウザにキャッシュ保存されたトークンを取得して JSON Web Token(JWT)の形式で有効な ID トークンを返します。トークンの有効期限がすでに切れている場合、この関数は Identity Platform API を呼び出してトークンを更新し、更新トークンを新しい ID トークンと交換します。

次のスニペットは、既存の ID トークンがまだ有効か期限切れかを確認する方法と、Identity Platform を呼び出して ID トークンを必要に応じて更新する方法を示しています。

IdentityPlatformAuthHelper.prototype.getIdToken = function() {
  const token = this.jwtDecode(this.user.idToken);

  // If exp has passed, refresh the token
  if (Date.now() > token.payload.exp * 1000) {
    return this.refreshToken(this.user.refreshToken);
  }
  return Promise.resolve(this.user.idToken);
}

IdentityPlatformAuthHelper.prototype.jwtDecode = function(t) {
  const token = {};
  token.raw = t;
  token.header = JSON.parse(window.atob(t.split('.')[0]));
  token.payload = JSON.parse(window.atob(t.split('.')[1]));
  return token;
}

IdentityPlatformAuthHelper.prototype.refreshToken = function(refreshToken) {
  // https://cloud.google.com/identity-platform/docs/reference/rest/client#section-refresh-token
  const tokenUrl = `https://securetoken.googleapis.com/v1/token?key=${config.apiKey}`;
  const requestBody = new URLSearchParams(`grant_type=refresh_token&refresh_token=${refreshToken}`);

  return fetch(
      tokenUrl,
      {
        contentType: 'application/x-www-form-urlencoded',
        method: 'POST',
        body: requestBody
      }
    )
  .then(response => response.json())
  .then(data => {
    this.user.idToken = data.id_token;
    this.user.refreshToken = data.refresh_token;
    return this.user.idToken;
  })
  .catch(error => {
    console.error(error);
  });
}

次の手順に沿って、Google の ID で Identity Platform にログインします。

  1. ウェブアプリのデフォルトのページを表示しているタブに戻ります。このタブをすでに閉じている場合は、[Cloud Shell] ページに戻り、[ウェブでプレビュー] をクリックしてから [ポート 8080 でプレビュー] をクリックします。新しいタブにウェブページが表示されるまで待ちます。
  2. ブラウザでアドレスを変更し、customer-info-with-api.html ページを表示します。新しい URL の形式は次のとおりです。 https://random_prefix-devshell.appspot.com/customer-info-with-api.html
  3. [Google でログイン] をクリックし、自身の認証情報でログインします。ログインすると、メールアドレスが記載されたテキスト ボックスが表示されます。

    JWT をデコードして、Identity Platform と Google により提供されるユーザー情報を確認するには、次の操作を行います。確認しない場合は、次の手順に進んでください。

    ユーザーの情報は、JWT のペイロード(2 番目)の部分にあり、base64 でエンコードされています。JWT の 2 番目の部分をデコードし、jq を使用して情報を JSON ファイルで出力するには、Cloud Shell で以下のコマンドを実行します。

    token=[PASTE_JWT_STRING_HERE]
    echo $token | awk '{split($0, a, "."); print a[2]; }' | base64 -d | jq
    

    他のドキュメントに関しても、Firestore に対するクエリを実行します。

  4. [Get customer info] をクリックします。自分の名前と会社名は、Firestore データベースに入力するときに表示されます。

  5. メールアドレスを bob@example.com に変更し、[Google でログイン] をクリックします。この場合のレスポンスは、次のエラー メッセージです。

    Error: "Missing or insufficient permissions." The security rule you added to Firestore limits your access to documents with an ID that matches your email address, as it appears in the token that Identity Platform created.

  6. このウェブページは閉じないでください。次の手順でこのページを使用します。

Identity Platform Client SDK を使用してログインする

Identity Platform に対するリクエストを手動で作成する代わりに、Identity Platform Client SDK を使用できます。この SDK はログイン プロセスを管理し、使用するプロバイダや、リダイレクトとポップアップのどちらを使用するかなど、ログインフローを制御する関数を提供します。

Identity Platform Client SDK を使用するには、HTML ページに複数のスクリプト ファイルを含める必要があります。次のスニペットは、必要なスクリプトを示しています。

<script src="https://www.gstatic.com/firebasejs/7.14.4/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.14.4/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.14.4/firebase-firestore.js"></script>

次のスニペットは、SDK を使用して Google プロバイダでログインする方法を示しています。

$('#sign-in').click((event) => {
  provider = new firebase.auth.GoogleAuthProvider();
  //firebase.auth().signInWithPopup(provider)
  firebase.auth().signInWithRedirect(provider)
  .then((result) => {
    console.log(result);
  })
  .catch((error) => {
    console.error(error);
  });
});

firebase.auth().onAuthStateChanged(function(user) {
  if (user) {
    $('#logged-out').hide();
    /* If the provider gives a display name, use the name for the
    personal welcome message. Otherwise, use the user's email. */
    const welcomeName = user.displayName ? user.displayName : user.email;
    console.log(firebase.auth().currentUser);
    $('#user').text(welcomeName);
    $('#logged-in').show();
    $('#email').val(firebase.auth().currentUser.email);
  } else {
    $('#logged-in').hide();
    $('#logged-out').show();
    $('#email').val('');
  }
  $('#customer-information').hide();
});

$('#sign-out').click(function(event) {
  firebase.auth().signOut().then(function() {
    console.log('Sign out successful');
  }, function(error) {
    console.error(error);
  });
});

firebase.auth().signInWithRedirect() 関数は、ユーザーをプロバイダのログインページにリダイレクトして、同じブラウザ ウィンドウでログイン プロセスを開始します。GoogleAuthProvider を使用すると、Google ログインフローを開始するようにこの関数に指示されます。

代わりに、signInWithPopup 関数を呼び出すと、リダイレクト動作をポップアップ動作に置き換えることができます。

他の認証プロバイダを使用する場合は、firebase.auth.AuthProvider インターフェースを実装する任意のタイプを追加します。必要なパラメータがすべて含まれるように、選択したプロバイダのドキュメントに従ってください。

firebase.auth().onAuthStateChanged 関数は、ログインとログアウトでトリガーされるオブザーバーです。ログイン時、ウェブアプリのコードが User オブジェクトから取得した情報をウェブページに入力して、ログインボタンを非表示にします。ログアウトすると、コードによりウェブページが消去され、ログインボタンが再び表示されます。

Identity Platform Client SDK は Firestore SDK と統合できます。Firestore SDK が、すべてのクエリで Identity Platform Client SDK から有効な ID トークンを取得して添付します。ID トークンの有効期限が切れた場合は、Identity Platform Client SDK が ID トークンを更新します。

次のコード スニペットは、Firestore SDK を使用して Firestore をクエリする方法を示しています。

function showCustomerInformation(userEmail) {
  $('#customer-information').show();
  $('#output').empty();

  const db = firebase.firestore();
  const collectionId = 'customers';

  query = db.collection(collectionId).doc(userEmail).get();
  query.then((doc) => {
    var fields = doc.data();
    $('#output').append($('<p>').text(`Id: ${doc.id}`));
    $('#output').append($('<p>').text(`Name: ${fields.name}`));
    $('#output').append($('<p>').text(`Company: ${fields.company}`));
  }).catch((error) => {
    console.error(error);
    $('#output').text("Error: " + error.toString());
  });
}

クエリに ID トークンを追加するためのコードを記述する必要はありません。Firestore SDK と Identity Platform Client SDK が認証プロセスを処理します。

次の手順で、Google の ID で Identity Platform にログインし、Firestore に対してクエリを実行します。

  1. 関連するウェブアプリのタブをすでに閉じている場合は、[Cloud Shell] ページに戻り、[ウェブでプレビュー] をクリックして、[ポート 8080 でプレビュー] をクリックします。新しいタブにウェブページが表示されるまで待ちます。
  2. ブラウザでアドレスを変更し、customer-info-with-sdk.html のページを表示します。新しい URL の形式は次のとおりです。https://random_prefix-devshell.appspot.com/customer-info-with-sdk.html
  3. [Google でログイン] をクリックし、自身の認証情報でログインします。ログインすると、自身のメールアドレスが記載されたテキスト ボックスが表示されます。
  4. [Get customer info] をクリックします。自分の名前と会社名は、Firestore データベースに入力するときに表示されます。
  5. メールアドレスを bob@example.com に変更し、[Google でログイン] をクリックします。この場合のレスポンスはエラー メッセージです。

    Error: FirebaseError: [code=permission-denied]: Missing or insufficient Permissions.

    JavaScript を使用して Firestore をクエリする方法の詳細については、Firestore のドキュメントをご覧ください。

ウェブアプリの問題のトラブルシューティング

ウェブアプリの実行中に以下の問題が発生した場合に役立つ、トラブルシューティング手順について説明します。

ウェブアプリの閲覧時のエラー

エラー
以下のエラー メッセージのいずれかが表示される。

Error:Could not connect to Cloud Shell on port 8080

Error:No active Cloud Shell
カスタム認証ハンドラを登録するの説明を確認し、Cloud Shell が開いていることと、ウェブアプリが実行中であることを確認してください。新しい Cloud Shell セッションを開く場合は、ウェブアプリを実行する前に、作業ディレクトリをウェブアプリのディレクトリに変更します。

cd "$HOME"/cloudshell_open/securing-cloud-firestore-with-identity-platform

ウェブアプリを実行した後、次の出力が表示されていることを確認します。

Example app listening on port 8080!

Google へのログイン時のエラー

エラー
[Google でログイン] をクリックしても何も起こらない。 次のエラーコードが表示される。

Cannot GET /undefined
ウェブ アプリケーションを構成するの説明を確認し、config.js ファイル内で apiKey 変数と authDomain 変数が設定されていることを確認してください。
Google ログインページに、次のエラー メッセージが表示される。

Authorization Error - Error 400: redirect_uri_mismatch
Google に送信されたリダイレクト URL が、OAuth クライアントで承認された URL のリストと一致しません。カスタム認証ハンドラを登録するの説明を確認し、承認済みのリダイレクト URI が構成されていることを確認してください。
Identity Platform SDK で Google にログインすると、次のエラー メッセージが表示される。

This domain (***) is not authorized to run this operation. Add it to the OAuth redirect domains list in the Firebase console -> Auth section -> Sign-in method tab
このエラーは、Cloud Shell で使用されるドメインが Identity Platform で許可されているドメインのリストに存在しない場合に表示されることがあります。ウェブアプリのドメインを承認するの手順を確認し、ドメイン名がドメインリストに追加されていることを確認してください。

顧客情報の取得時のエラー

エラー
以下のエラー メッセージのいずれかが表示される。

Error: FirebaseError: [code=permission-denied]: Missing or insufficient permissions

Error: Missing or insufficient permissions
Firestore のセキュリティ ルールが Firestore に送信されたトークンを検証できなかった可能性があります。Firestore セキュリティ ルールを作成するの説明を確認して、Firestore のセキュリティ ルールが正しく構成されていることを確認してください。
以下のエラー メッセージのいずれかが表示される。

Error: FirebaseError: [code=unavailable]: Failed to get document because the client is offline

Error: "The project *** does not exist or it does not contain an active Datastore or Firestore database

Error: "Project id [PROJECT_ID] is malformed: it either contains invalid characters or is too long
ウェブ アプリケーションを構成するの説明を確認し、config.js ファイルに projectId プロパティが設定されていることを確認してください。

クリーンアップ

このチュートリアルで使用したリソースについて、Google Cloud アカウントに課金されないようにするには、リソースを含むプロジェクトを削除するか、プロジェクトを維持して個々のリソースを削除します。

  1. In the Google Cloud console, go to the Manage resources page.

    Go to Manage resources

  2. In the project list, select the project that you want to delete, and then click Delete.
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

リソースを個別に削除する

  1. Cloud Shell で、Ctrl+C を押してウェブアプリを停止します。
  2. クローンを作成したリポジトリ ディレクトリを削除します。

    rm -rf "$HOME"/cloudshell_open/securing-cloud-firestore-with-identity-platform
    
  3. Identity Platform の [設定] ページに移動します。

    Identity Platform の [設定] ページに移動

  4. [セキュリティ] タブをクリックし、以前に追加したドメインを削除してから [保存] をクリックします。

  5. [ID プロバイダ] ページに移動します。

    [ID プロバイダ] ページに移動

  6. 以前に追加した Google プロバイダを削除します。

  7. [API とサービス] に移動し、[認証情報] ページに移動します。

    [ドメインの確認] ページに移動

  8. [認証情報] ページで、[Web client (auto created by Google Service)] をクリックします。

  9. [承認済みのリダイレクト URI] ページで、先ほど貼り付けた URL を削除してから [保存] をクリックします。

  10. Firebase コンソールを開き、コンソールのナビゲーション パネルでプロジェクトをクリックします。

  11. 左側にある [開発] メニューから [データベース] オプションを選択します。

  12. [データベース] ページで [ルール] タブをクリックします。

  13. 現在のセキュリティ ルールをドキュメントの開始前に使用していたルールで上書きして、[公開] をクリックします。

  14. Google Cloud コンソールで [Firestore] ページに移動します。

  15. customers コレクションの左側にあるメニューをクリックし、[コレクションを削除] をクリックします。

  16. 削除の確認ポップアップで、[コレクション ID] フィールドに「customers」と入力し、[削除] をクリックします。

次のステップ

Google Cloud に関するリファレンス アーキテクチャ、図、ベスト プラクティスを確認する。Cloud アーキテクチャ センター をご覧ください。