App Engine でのユーザーのログイン


このチュートリアルでは、Identity Platform、App Engine スタンダード環境、Datastore を使用して、サードパーティの認証情報を取得、検証、保存する方法を説明します。

本書では、Firenotes という名前のシンプルなメモ編集アプリケーションの使い方について説明します。このアプリケーションは、ユーザーのメモを個人用のノートブックに保存します。ノートブックはユーザーごとに保存され、各ユーザーの一意の Identity Platform ID で識別されます。このアプリケーションには次のコンポーネントがあります。

  • フロントエンドはログイン ユーザー インターフェースを構成し、Identity Platform ID を取得します。また、認証状態の変更を処理し、ユーザーにメモを表示します。

  • FirebaseUI は、認証と UI タスクを簡素化するオープンソースのドロップイン ソリューションです。SDK は、ユーザー ログイン、複数のプロバイダの 1 つのアカウントへのリンク、パスワードの回復などを処理します。認証のベスト プラクティスを実装して、スムーズで安全なログイン方式を実現します。

  • バックエンドは、ユーザーの認証状態を検証し、ユーザーのプロフィール情報およびメモを返します。

アプリケーションは NDB クライアント ライブラリを使用してユーザー認証情報を Datastore に保存しますが、認証情報は任意のデータベースに保存できます。

Firenotes は、Flask ウェブ アプリケーション フレームワークをベースにしています。サンプルアプリが Flask を使用しているのは、単純で使いやすいからです。ただし、ここで説明する概念やテクノロジーは、使用するフレームワークに関係なく、適用可能です。

目標

このチュートリアルの目標は次のとおりです。

  • Identity Platform 用の FirebaseUI でユーザー インターフェースを構成する。
  • Identity Platform の ID トークンを取得し、サーバー側の認証を使用して検証する。
  • ユーザー認証情報と関連データを Datastore に保存する。
  • NDB クライアント ライブラリを使用してデータベースに問い合わせる。
  • アプリを App Engine にデプロイする。

費用

このチュートリアルでは、Google Cloud の課金対象となる以下のコンポーネントを使用します。

  • Datastore
  • Identity Platform

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

始める前に

  1. GitPython 2.7virtualenv をインストールします。Python の最新バージョンのインストールなど、Python 開発環境の詳しい設定については、Google Cloud の Python 開発環境のセットアップをご覧ください。
  2. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  3. Google Cloud Console の [プロジェクト セレクタ] ページで、Google Cloud プロジェクトを選択または作成します。

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

  4. Google Cloud CLI をインストールします。
  5. gcloud CLI を初期化するには:

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

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

  7. Google Cloud CLI をインストールします。
  8. gcloud CLI を初期化するには:

    gcloud init

すでに SDK をインストールして別のプロジェクトに初期化してある場合は、gcloud プロジェクトを Firenotes に使用している App Engine プロジェクト ID に設定します。gcloud ツールを使用してプロジェクトを更新するための個別のコマンドについては、Google Cloud SDK 構成の管理をご覧ください。

サンプルアプリのクローン作成

サンプルをローカルマシンにダウンロードするには、次のようにします。

  1. サンプル アプリケーション レポジトリをローカルマシンにクローン作成します。

    git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git

    または、zip ファイルとしてサンプルをダウンロードして解凍することもできます。

  2. サンプルコードが含まれるディレクトリに移動します。

    cd python-docs-samples/appengine/standard/firebase/firenotes
    

ユーザー インターフェースの追加

Identity Platform 用の FirebaseUI を構成して、ID プロバイダを有効にするには:

  1. 次の手順に従い、Identity Platform をアプリに追加します。

    1. Google Cloud コンソールに移動します。
      Google Cloud コンソールに移動
    2. 使用する Google Cloud プロジェクトを選択します。
      • 既存のプロジェクトがある場合は、ページ上部の [組織の選択] プルダウン リストからプロジェクトを選択します。
      • 既存の Google Cloud プロジェクトがない場合は、Google Cloud コンソールで新しいプロジェクトを作成します。
    3. Google Cloud コンソールで Identity Platform の Marketplace ページに移動します。
      Identity Platform の Marketplace ページに移動
    4. Identity Platform の Marketplace ページで、[お客様 ID を有効にする] をクリックします。
    5. Google Cloud コンソールのお客様 ID の [ユーザー] ページに移動します。
      [ユーザー] ページに移動
    6. 右上の [アプリケーション設定の詳細] をクリックします。
    7. アプリケーション設定の詳細をウェブ アプリケーションにコピーします。

      // Obtain the following from the "Add Firebase to your web app" dialogue
      // Initialize Firebase
      var config = {
        apiKey: "<API_KEY>",
        authDomain: "<PROJECT_ID>.firebaseapp.com",
        databaseURL: "https://<DATABASE_NAME>.firebaseio.com",
        projectId: "<PROJECT_ID>",
        storageBucket: "<BUCKET>.appspot.com",
        messagingSenderId: "<MESSAGING_SENDER_ID>"
      };
  2. backend/app.yaml ファイルを編集し、Google Cloud プロジェクト ID を環境変数に入力します。

    # Copyright 2021 Google LLC
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #      http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    
    runtime: python27
    api_version: 1
    threadsafe: true
    service: backend
    
    handlers:
    - url: /.*
      script: main.app
    
    env_variables:
      GAE_USE_SOCKETS_HTTPLIB : 'true'
    
  3. frontend/main.js ファイルで、ユーザーに提供するプロバイダを選択して FirebaseUI ログイン ウィジェットを構成します。

    // Firebase log-in widget
    function configureFirebaseLoginWidget() {
      var uiConfig = {
        'signInSuccessUrl': '/',
        'signInOptions': [
          // Leave the lines as is for the providers you want to offer your users.
          firebase.auth.GoogleAuthProvider.PROVIDER_ID,
          firebase.auth.FacebookAuthProvider.PROVIDER_ID,
          firebase.auth.TwitterAuthProvider.PROVIDER_ID,
          firebase.auth.GithubAuthProvider.PROVIDER_ID,
          firebase.auth.EmailAuthProvider.PROVIDER_ID
        ],
        // Terms of service url
        'tosUrl': '<your-tos-url>',
      };
    
      var ui = new firebaseui.auth.AuthUI(firebase.auth());
      ui.start('#firebaseui-auth-container', uiConfig);
    }
  4. Google Cloud コンソールで、保持することを選択したプロバイダを有効にするには、次のようにします。

    1. Google Cloud コンソールのお客様 ID の [プロバイダ] ページに移動します。
      [プロバイダ] ページに移動
    2. [プロバイダを追加] をクリックします。
    3. [プロバイダの選択] プルダウン リストで、使用するプロバイダを選択します。
    4. [有効] の横にあるボタンをクリックして、プロバイダを有効にします。
      • サードパーティの ID プロバイダの場合は、プロバイダのデベロッパー サイトから、プロバイダ ID とシークレットを入力します。Firebase ドキュメントの、FacebookTwitter、および GitHub ガイドの「始める前に」に、具体的な指示が記載されています。
      • SAML と OIDC の統合については、ご自身の IdP の構成を参照してください。
  5. Identity Platform で承認済みドメインのリストにご自身のドメインを追加します。

    1. Google Cloud コンソールのお客様 ID の [設定] ページに移動します。
      [設定] ページに移動
    2. [承認済みドメイン] で、[ドメインの追加] をクリックします。
    3. アプリのドメインを次の形式で入力します。

      [PROJECT_ID].appspot.com
      

      ドメイン名の前に http:// を含めないでください。

依存関係のインストール

  1. backend ディレクトリに移動して、アプリケーション セットアップを完了します。

    cd backend/
    
  2. 依存関係をプロジェクトの lib ディレクトリにインストールします。

    pip install -t lib -r requirements.txt
    
  3. appengine_config.py で、vendor.add() メソッドがライブラリを lib ディレクトリに登録します。

アプリケーションをローカルで実行する

アプリケーションをローカルで実行するには、App Engine ローカル開発用サーバーを使用します。

  1. main.js で次の URL を backendHostURL として追加します。

    http://localhost:8081

  2. アプリケーションのルート ディレクトリに移動します。その後で、開発用サーバーを始動します。

    dev_appserver.py frontend/app.yaml backend/app.yaml
    
  3. ウェブブラウザで http://localhost:8080/ にアクセスします。

サーバーでユーザーを認証する

これで、プロジェクトのセットアップと開発用のアプリケーションの初期化が完了しました。コードをたどりながら、サーバー上で Identity Platform ID トークンを取得して確認する方法を見てみましょう。

Identity Platform から ID トークンを取得する

サーバー側の認証の最初の手順は、アクセス トークンの取得と確認です。認証リクエストは、Identity Platform からの onAuthStateChanged() リスナーを使用して処理されます。

firebase.auth().onAuthStateChanged(function(user) {
  if (user) {
    $('#logged-out').hide();
    var name = user.displayName;

    /* If the provider gives a display name, use the name for the
    personal welcome message. Otherwise, use the user's email. */
    var welcomeName = name ? name : user.email;

    user.getIdToken().then(function(idToken) {
      userIdToken = idToken;

      /* Now that the user is authenicated, fetch the notes. */
      fetchNotes();

      $('#user').text(welcomeName);
      $('#logged-in').show();

    });

  } else {
    $('#logged-in').hide();
    $('#logged-out').show();

  }
});

ユーザーがログインすると、コールバックの Identity Platform getToken() メソッドが、JSON Web Token(JWT)形式で Identity Platform ID トークンを返します。

サーバーでトークンを確認する

ユーザーがログインすると、フロントエンド サービスが AJAX GET リクエストを通してユーザーのノートブック内の既存のメモをフェッチします。これには、ユーザーのデータにアクセスする認可が必要なため、JWT が Bearer スキーマを使用してリクエストの Authorization ヘッダーで送信されます。

// Fetch notes from the backend.
function fetchNotes() {
  $.ajax(backendHostUrl + '/notes', {
    /* Set header for the XMLHttpRequest to get data from the web server
    associated with userIdToken */
    headers: {
      'Authorization': 'Bearer ' + userIdToken
    }
  })

クライアントがサーバーデータにアクセスするには、トークンが Identity Platform によって署名されていることを、サーバーで確認する必要があります。このトークンは、Python 用の Google 認証ライブラリを使用して確認できます。認証ライブラリの verify_firebase_token 関数を使用して、署名なしトークンを確認し、クレームを抽出します。

id_token = request.headers["Authorization"].split(" ").pop()
claims = google.oauth2.id_token.verify_firebase_token(
    id_token, HTTP_REQUEST, audience=os.environ.get("GOOGLE_CLOUD_PROJECT")
)
if not claims:
    return "Unauthorized", 401

ID プロバイダごとに異なる一連のクレームが送信されますが、それぞれに少なくとも一意のユーザー ID を持つ 1 つの sub クレームと、アプリのユーザー エクスペリエンスのカスタマイズに使用できるプロフィール情報(nameemail など)を提供する 1 つのクレームが含まれます。

Datastore でのユーザーデータの管理

ユーザーを認証したら、それに関するユーザーのデータを保存してログイン セッションが終わるまで維持する必要があります。以降のセクションでは、メモを Datastore エンティティとして保存し、それらをユーザー ID で区別する方法について説明します。

ユーザーデータを保存するエンティティの作成

Datastore では、整数や文字列などの特定のプロパティを使用し、NDB モデルクラスを宣言することによってエンティティを作成できます。Datastore は、種類を基準にしてエンティティにインデックスを付けます。Firenotes の場合、各エンティティの種類は Note です。クエリを出すために、各 Note はキー名付きで保存されます。キー名は、前のセクションの sub クレームから取得されたユーザー ID です。

次のコードは、エンティティのプロパティを設定する方法を示し、エンティティの作成時にモデルクラスとしてコンストラクタ メソッドを使用する方法と、作成後に個別のプロパティを割り当てる方法の両方が示されています。

data = request.get_json()

# Populates note properties according to the model,
# with the user ID as the key name.
note = Note(parent=ndb.Key(Note, claims["sub"]), message=data["message"])

# Some providers do not provide one of these so either can be used.
note.friendly_id = claims.get("name", claims.get("email", "Unknown"))

新しく作成した Note を Datastore に書き込むには、note オブジェクトに対して put() メソッドを呼び出します。

ユーザーデータの取得

特定のユーザー ID に関連付けられたユーザーデータを取得するには、NDB の query() メソッドを使用してデータベースを検索し、同じエンティティ グループ内のメモを見つけます。同じグループ内のエンティティ、つまり、祖先パスは、共通のキー名(この場合はユーザー ID)を共有します。

def query_database(user_id):
    """Fetches all notes associated with user_id.

    Notes are ordered them by date created, with most recent note added
    first.
    """
    ancestor_key = ndb.Key(Note, user_id)
    query = Note.query(ancestor=ancestor_key).order(-Note.created)
    notes = query.fetch()

    note_messages = []

    for note in notes:
        note_messages.append(
            {
                "friendly_id": note.friendly_id,
                "message": note.message,
                "created": note.created,
            }
        )

    return note_messages

クエリデータをフェッチして、メモをクライアントに表示できます。

// Fetch notes from the backend.
function fetchNotes() {
  $.ajax(backendHostUrl + '/notes', {
    /* Set header for the XMLHttpRequest to get data from the web server
    associated with userIdToken */
    headers: {
      'Authorization': 'Bearer ' + userIdToken
    }
  }).then(function(data){
    $('#notes-container').empty();
    // Iterate over user data to display user's notes from database.
    data.forEach(function(note){
      $('#notes-container').append($('<p>').text(note.message));
    });
  });
}

アプリのデプロイ

これで Identity Platform と App Engine アプリケーションが正常に統合されました。本番環境で動作しているアプリケーションを確認するには、次の手順を実行します。

  1. main.js のバックエンド ホスト URL を https://backend-dot-[PROJECT_ID].appspot.com に変更します。[PROJECT_ID] は実際のプロジェクト ID に置き換えます。
  2. Google Cloud SDK コマンドライン インターフェースを使用してアプリケーションをデプロイします。

    gcloud app deploy backend/index.yaml frontend/app.yaml backend/app.yaml
    
  3. https://[PROJECT_ID].appspot.com で稼働中のアプリケーションを表示します。

クリーンアップ

このチュートリアルで使用したリソースについて、Google Cloud アカウントに課金されないようにするために、以下の手順で App Engine プロジェクトを削除します。

プロジェクトの削除

課金をなくす最も簡単な方法は、チュートリアル用に作成したプロジェクトを削除することです。

プロジェクトを削除するには:

  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.

次のステップ