Firebase を使用した App Engine 上でのユーザーの認証

このチュートリアルでは、Firebase Authentication、App Engine スタンダード環境、および Cloud Datastore を使用してユーザー認証情報を取得、検証、保存する方法を説明します。

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

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

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

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

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

次の図は、フロントエンドとバックエンドが相互に通信する方法、およびユーザー認証情報が Firebase からデータベースにどのように移動するかを示しています。
リクエストのパスとユーザー認証情報

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

目標

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

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

費用

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

  • Cloud Datastore

料金計算ツールを使うと、予想使用量に基づいて費用の見積もりを出すことができます。 GCP を初めて使用される方は、無料トライアルをご利用いただけます。

始める前に

  1. GitPython 2.7、および virtualenv をインストールします。最新バージョンの Python のインストールなど Python 開発環境の設定の詳細については、GCP の Python 開発環境の設定を参照してください。
  2. Google アカウントにログインします。

    Google アカウントをまだお持ちでない場合は、新しいアカウントを登録します。

  3. GCP Console のプロジェクト セレクタのページで、GCP プロジェクトを選択または作成します。

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

  4. Cloud SDK をインストールして初期化します。

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

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

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

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

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

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

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

    cd python-docs-samples/appengine/standard/firebase/firenotes
    
    FirebaseUI を設定して ID プロバイダを有効にするには、次のようにします。

  3. Firebase をアプリに追加する手順は次のとおりです。

    1. Firebase コンソールで Firebase プロジェクトを作成します。
      • 既存の Firebase プロジェクトがない場合は、[プロジェクトを追加] をクリックし、既存の Google Cloud Platform プロジェクト名または新しいプロジェクト名を入力します。
      • 既存の Firebase プロジェクトを使用する場合は、そのプロジェクトをコンソールから選択します。
    2. プロジェクトの概要ページで、[ウェブアプリに Firebase を追加] をクリックします。プロジェクトにアプリがある場合は、プロジェクトの概要ページで [アプリを追加] を選択します。
    3. プロジェクトのカスタマイズ済みのコード スニペットの Initialize Firebase セクションを使用して、frontend/main.js ファイルの次のセクションを入力します。

      // 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>"
      };
  4. backend/app.yaml ファイルを編集し、Firebase プロジェクト ID を環境変数に入力します。

    runtime: python27
    api_version: 1
    threadsafe: true
    service: backend
    
    handlers:
    - url: /.*
      script: main.app
    
    env_variables:
      # Replace with your Firebase project ID.
      FIREBASE_PROJECT_ID: '<PROJECT_ID>'
    
  5. 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);
    }
  6. [認証] > [ログイン方法] をクリックして、選択したプロバイダが Firebase コンソール内に保持されるようにします。その後、[ログイン プロバイダ] で、カーソルをプロバイダの上に移動し、鉛筆アイコンをクリックします。

    プロバイダにログイン

    1. [有効] ボタンを切り替え、サードパーティ ID プロバイダの場合は、プロバイダのデベロッパー サイトからプロバイダ ID とシークレットを入力します。Firebase ドキュメントの、FacebookTwitter、および GitHub ガイドの「始める前に」に、具体的な指示が記載されています。プロバイダを有効にしたら、[保存] をクリックします。

      有効ボタンの切り替え

    2. Firebase コンソールの [承認済みドメイン] で [ドメインを追加] をクリックし、App Engine のアプリのドメインを次の形式で入力します。

      [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/ にアクセスします。

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

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

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

サーバー側の認証の最初の手順は、アクセス トークンの取得と確認です。認証リクエストは、Firebase からの 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.getToken().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();

  }

ユーザーがログインすると、コールバックの Firebase getToken() メソッドが JSON Web Token(JWT)形式で Firebase 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
    }
  })

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

id_token = request.headers['Authorization'].split(' ').pop()
claims = google.oauth2.id_token.verify_firebase_token(
    id_token, HTTP_REQUEST)
if not claims:
    return 'Unauthorized', 401

各 ID プロバイダは別々の要求セットを送信しますが、それぞれに一意のユーザー ID を持つ 1 つ以上の sub 要求と nameemail などの特定のプロフィール情報を提供する 1 つの要求が含まれています。これを使用して、アプリのユーザー エクスペリエンスをパーソナライズすることができます。

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

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

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

Cloud Datastore では、整数や文字列などの特定のプロパティを使用し、NDB モデルクラスを宣言することによってエンティティを作成できます。Cloud 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 を Cloud 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));
    });
  });
}

アプリのデプロイ

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

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

    gcloud app deploy backend/index.yaml frontend/app.yaml backend/app.yaml
    
  3. https://[PROJECT_ID].appspot.com で実動しているアプリケーションを表示します。

クリーンアップ

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

プロジェクトの削除

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

プロジェクトを削除する手順は次のとおりです。

  1. GCP Console で [プロジェクト] ページに移動します。

    プロジェクト ページに移動

  2. プロジェクト リストで、削除するプロジェクトを選択し、[削除] をクリックします。
  3. ダイアログでプロジェクト ID を入力し、[シャットダウン] をクリックしてプロジェクトを削除します。

次のステップ

このページは役立ちましたか?評価をお願いいたします。

フィードバックを送信...

Python 2 の App Engine スタンダード環境