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

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

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

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

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

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

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

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

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

目標

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

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

費用

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

  • Datastore

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

始める前に

  1. GitPython 2.7virtualenv をインストールします。Python の最新バージョンのインストールなど、Python 開発環境の詳しい設定については、Google Cloud の Python 開発環境のセットアップをご覧ください。
  2. Google Cloud アカウントにログインします。Google Cloud を初めて使用する場合は、アカウントを作成して、実際のシナリオでの Google プロダクトのパフォーマンスを評価してください。新規のお客様には、ワークロードの実行、テスト、デプロイができる無料クレジット $300 分を差し上げます。
  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
    
    FirebaseUI を設定して ID プロバイダを有効にするには、次のようにします。

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

    1. Firebase コンソールで Firebase プロジェクトを作成します。
      • 既存の Firebase プロジェクトがない場合は、[プロジェクトを追加] をクリックし、既存の Google Cloud プロジェクト名か新しいプロジェクト名を入力します。
      • 既存の 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. 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);
    }
  5. [認証] > [ログイン方法] をクリックして、選択したプロバイダが Firebase コンソール内に保持されるようにします。その後、[ログイン プロバイダ] で、カーソルをプロバイダの上に移動し、鉛筆アイコンをクリックします。

    ログイン プロバイダ

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

      有効ボタンの切り替え

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

  }
});

ユーザーがログインすると、コールバックの 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, 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));
    });
  });
}

アプリのデプロイ

これで Firebase Authentication と 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. Google Cloud コンソールで、[リソースの管理] ページに移動します。

    [リソースの管理] に移動

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

次のステップ

  • Google Cloud に関するリファレンス アーキテクチャ、図、チュートリアル、ベスト プラクティスを確認する。Cloud Architecture Center を確認します。