top pageへ.

Programming Room.

Google Cloud Tips.

Identity Platform. 2020/04/05
更新 2002/05/07
更新 2020/06/10
更新 2021/05/01

 GAEからIdentity Platformを使う方法の説明です。


おしながき

Identity Platformの概要.

概要.

 Identity Platformは、Webアプリ/モバイルアプリで必要とされる、ユーザの登録/抹消/ログイン/ログアウトといった、ユーザ認証に関する操作を実装するためのサービスです。メールアドレスとパスワードの組み合わせだけでなく、匿名ログインや、OpenIDをサポートするGoogle以外のWebサービスのアカウントを使用した認証もサポートしています。ユーザ認証の実装がちょっと簡単になる程度の簡易なものではなく、商用サービスをターゲットにした、エンタープライズ向けのサービスです。
 またパスワードの管理もやってくれるので、パスワードの漏洩の対処を任せておける便利さもあります。

 もともとはWebアプリ/モバイルアプリ向けのバックエンドサービスであるFirebaseの一機能だったものに、GCPの一部として「Identity Platform」の名前を付けただけのようです。そのためドキュメントやコンソールが、GCPとFirebaseで別々に用意されています。コンソールの方は機能的にはほぼ同じのようで、どちらを使っても問題はありませんでしたし、一方のコンソールで設定の変更を行えば即座に他方に反映されました。しかしドキュメントはFirebaseの方がよさそうです。GCPだと英語しか用意されていなくても、Firebaseなら日本語訳があったりします。ページ構成は完全に1対1で対応しているわけではなさそうなので、GCPで見つけた説明の日本語訳を探してFirebaseを覘いても、対応するページを探すのは大変かもしれませんが。
 Identity PlatformのURIは、"firebase"のパスで実装されていたりします。


何ができるの.

 すべてを試したわけではないので、細かいところは省いて代表的なところだけ。


Users APIってやつ.

 GAEにはUsers APIというものも実装されています。APIリファレンスから該当するクラスを探すと、UserUserServiceUserServiceFactoryあたりですね。
 これでもよさそうな気がするのですが、大きな欠陥があります。

 「ユーザーとデータストア」には『UserオブジェクトをそのままDatastoreに保存するな』と記述されています。ユーザはメールアドレスを後から変更する可能性があり、その場合に保存してあるものと一致しなくなるからという理由です。その代わりにUser.getUserId()で得られるユーザIDをキーとして保存することを求められています。これはこれでいいのですが、このUser.getUserId()で得たユーザIDから、Userオブジェクトを取得する手段が用意されていません。Userクラスはカレントユーザの情報を取得する際に使うだけで、アプリが管理するユーザはUserクラスに依存しないように実装する必要が出てきます。
 またUser.getNickname()で得られる文字列は、単にメールアドレスのユーザ部分("@"の前)を返すだけであり、Googleアカウントの表示名とは異なります。そしてGoogleアカウントの表示名を得る手段は見つかりません。

 期待した仕様と違う、残念なAPIです。


用語.

 Identity Platformでは独自の用語が登場します。主なものを説明します。

用語 説明
IDプロバイダ ユーザ認証とユーザIDを提供するサービス。
フェデレーションIDプロバイダ IDプロバイダのうち、Google/Facebook/Twitter/Github。「フェデレーション」を直訳すると「連合」とか、しっくりこない日本語になりますので、Firebaseの公式ドキュメントを参考にしました。
IDトークン ユーザを識別するための文字列。

設定.

 GAEアプリにIdentity Platformを利用してユーザ認証を実装する方法を説明します。
 まずはGCPコンソールで設定が必要です。設定すべき事柄は、順に以下のとおり。

  1. Identity Platformの有効化。
  2. IDプロバイダの設定。
  3. 承認済みドメインの追加。

 Firebaseコンソールだと表現が異なります。「Identity Platform」「IDプラットフォーム」→「Authentication」に脳内変換して、以下読んでください。


Identity Platformの有効化.

 GCPのドキュメントでは「Adding the user interface」に説明されています。
 Identity Platformは初期状態では無効になっていますので、有効化する必要があります。GCPコンソールを日本語に設定していると、ナビゲーションメニューには「IDプラットフォーム」と表示されています。最初にこの「IDプラットフォーム」にアクセスした時に有効化するかどうか聞かれるので、有効にします。


 


IDプロバイダの設定.

 利用したいIDプロバイダを決め、それをGCPコンソールの「IDプロバイダ」で1つずつ追加・設定します。
 「プロバイダを追加」ボタンを押すと、ログインに使うIDプロバイダの選択画面に遷移します。複数使いたい場合でも1つずつ追加するので、まずはどれか一つをドロップダウンリストから選んでください。すでに追加済みのIDプロバイダはグレーアウトされ、選択できません。

 IDプロバイダを選択すると、そのIDプロバイダの設定項目が、ドロップダウンリストの下に表示されます。
 IDプロバイダにより設定項目の内容は異なりますので、別ページにIDプロバイダごとの設定方法をまとめました。


承認済みドメインの追加.

 何か1つでもIDプロバイダを追加したら、承認済みドメインの追加が可能になります。承認済みドメインの設定は、すべてのIDプロバイダで共通です。デフォルトではGAEアプリが登録されていませんので、手作業で登録します。


 追加済みのIDプロバイダの一覧から適当なIDプロバイダの鉛筆アイコンをクリックして、そのIDプロバイダの設定ページを表示します。すると右のように、画面右に「承認済みドメイン」の一覧が表示されます。デフォルトではFirebaseで使用されるドメインが登録されていますが、GAEアプリのドメインは登録されていません。すでにGAEのアプリが登録されていることを確認したら、これ以降は読み飛ばして次へ進みます。


 GAEアプリのドメインがないことを確認したら、「ドメインの追加」ボタンを押します。
 ドメインの入力ダイアログが表示されるので、GAEアプリのドメインを入力します。「https://」は必要ありません。「承認済みドメイン」の一覧に入力したGAEアプリのドメインが追加されているのを確認したら完了です。


 Firebaseコンソールの場合は、「ログインプロバイダ」のリストの下に「承認済みドメイン」があります。


ユーザ認証の実装.

クライアントの実装.

 実装はクライアントとサーバに分かれます。まずはHTML/CSS/JavaScriptで実装するクライアント側から。
 ドキュメントは、GCPなら「Authenticating users on App Engine using Identity Platform」、Firebaseなら「FirebaseUI でウェブアプリに簡単にログイン機能を追加する」あたりが参考になります。


Firebase JavaScript SDKの読み込み.

 ロジックのライブラリがJavaScriptで用意されているので、ログインを実装するページのHTMLで、それを読み込みます。公式ドキュメントではGCP「Adding the user interface」Firebase「ステップ3:Firebase SDKを追加してFirebaseを初期化する」でバージョンが違っていたりしますが、GCPコンソールの「IDプロバイダ」ページの「設定の詳細」リンクで表示されるポップアップ内のスニペットのバージョンが最新のようです。
 ただしこのスニペットに含まれる"firebase.js"は認証以外のすべてのFirebaseの機能を含んでいるので、安全上の問題があるとのことです。認証だけに絞るには以下のようにする必要がありました。

<html>
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    <script src="https://www.gstatic.com/firebasejs/7.11.0/firebase-app.js"></script>
    <script src="https://www.gstatic.com/firebasejs/7.11.0/firebase-auth.js"></script>
    ...

FirebaseUI for Web - Authの読み込み.

 ロジックとは別に、UIはJavaScript/CSSで用意されています。"-ui"を含む名称になっています。詳細は「FirebaseUI」に説明されています。最新バージョンはこのページの「Installation」の項を参照するのが良さそうです。最新版を探すのは苦労しました。
 あまり古いバージョンだと対応するIDプロバイダが少ないようです。

    ...
    <script src="https://www.gstatic.com/firebasejs/ui/4.5.0/firebase-ui-auth.js"></script>
    <link type="text/css" rel="stylesheet" href="https://www.gstatic.com/firebasejs/ui/4.5.0/firebase-ui-auth.css"/>
  </head>
  ...

 上記はデフォルトの英語版UIですが、各言語でローカライズされたバージョンも用意されています。「Localized Widget」の項に説明されているとおり、JavaScriptライブラリのみ、拡張子の前に言語コードを追加すれば、その言語のローカライズドバージョンが使用できます。日本語だと以下の様になります。言語コードの直前の文字は、アンダーバー"_"2個です。その他の使用可能な言語とその言語コードは、「Supported Languages」にリストされています。

    ...
    <script src="https://www.gstatic.com/firebasejs/ui/4.5.0/firebase-ui-auth__ja.js"></script>
    <link type="text/css" rel="stylesheet" href="https://www.gstatic.com/firebasejs/ui/4.5.0/firebase-ui-auth.css"/>
    ...

Firebase JavaScript SDKの初期化.

 「Adding the user interface」の1.のg.に説明されていますが、認証以外の余計なものまで含まれています。認証のみなら、下記で十分のようです。<API_KEY>と<PROJECT_ID>は、「IDプロバイダ」ページの「設定の詳細」リンクで表示されるポップアップ内のスニペットに表示されているとおりです。

  <body>
    <script type="text/javascript">
      var firebaseConfig = {
        apiKey: '<API_KEY>',
        authDomain: '<PROJECT_ID>.firebaseapp.com',
      };
      firebase.initializeApp(firebaseConfig);

      ... ここからFirebase Authを利用可能
    </script>
  </body>


認証UIの初期化と実行.

 「Adding the user interface」の3.に説明されているとおりですが、JavaScript SDKの初期化が終わったら、以下のようにFirebase UIを初期化・実行できます。「Starting the sign-in flow」が参考になります。
 signInOptionsプロパティの値は配列であり、ここで指定したIDプロバイダのログインボタンが、この順番で、AuthUI.start()メソッドの第1引数に指定した要素内に表示されます。

<html>
  <body>
    <!-- ログインUIを表示するためのコンテナ要素 -->
    <div id="firebaseui-auth-container"></div>

    <script type="text/javascript">
      ... Firebase JavaScript SDKの初期化

      var uiConfig = {
        'signInSuccessUrl': 'ログイン成功時の遷移先URI',
        'signInOptions': [
          firebase.auth.GoogleAuthProvider.PROVIDER_ID,
          firebase.auth.TwitterAuthProvider.PROVIDER_ID,
          firebase.auth.FacebookAuthProvider.PROVIDER_ID,
          firebase.auth.EmailAuthProvider.PROVIDER_ID,
        ],
        // Terms of service url
        'tosUrl': '利用規約ページのURI',
        // Privacy policy url.
        'privacyPolicyUrl': プライバシーポリシーページのURI',
      };

      var ui = new firebaseui.auth.AuthUI(firebase.auth());
      ui.start('#firebaseui-auth-container', uiConfig);
    </script>
  </body>
</html>

 tosUrlプロパティとprivacyPolicyUrlプロパティを両方指定すると、右の様にログインボタンの下に、これらのURLへのハイパーリンクを含むメッセージが追加されます。どちらか片方だけだと、このメッセージは表示されません。


IDトークンの取得とサーバへのリクエスト.

 ログインしたユーザの情報をサーバに渡すには、IDトークンを使用します。このIDトークンは、firebase.auth.Auth.onAuthStateChanged()メソッドの第1引数に渡すコールバック関数で受け取るfirebase.auth.Userクラスのオブジェクトで取得します。このコールバック関数は「Getting an ID token from Identity Platform」のサンプルのように実装します。サンプルから余計なものを取り除いて説明を加えたのが、以下のコードです。
 サーバにIDトークンを渡す際は、Authorizationヘッダで渡します。値は、値のタイプを表す"Bearer"とIDトークンの間に、区切りとして半角スペースが1つあることに注意してください。

<html>
  <body>
    <script type="text/javascript">
      var userIdToken;        // IDトークン保存用

      ... Firebase JavaScript SDKの初期化
      ... 認証UIの実行

      firebase.auth().onAuthStateChanged(function(user) {
	  if (user) {
          // 任意 : ユーザの表示名を決定する。
          // user.displayNameが提供されるならそれを、提供されないなら代わりにuser.emailを使用しています。
          var dispname = user.displayName? user.displayName: user.email;
          ... disnameを必要なら適当に表示

          // IDトークンの取得
          user.getIdToken().then(function(idtoken) {
            // IDトークンはサーバへのリクエストのたびに必要になるので、保存。
            userIdToken = idtoken;

            // GAEアプリに実装した、ログインがあったことをサーバに通知するエンドポイントをたたく
            $.ajax({
              type: 'GET',
              url: 'ログイン通知エンドポイントのURI',
              headers: {
                'Authorization': 'Bearer ' +userIdToken,        // AuthorizationヘッダでIDトークンを渡す
              },
            });
          });
        } else {
          // ログアウト
          userIdToken = null;        // IDトークンはもう無効なので破棄
        }
      });
    </script>
  </body>
</html>

 firebase.auth.Userクラスでは以下のプロパティより、ユーザの情報を取得できます。使いそうななものだけ。

プロパティ名 値の型 説明
displayName String IDプロバイダから渡されたユーザの表示名。IDプロバイダによってはnullの場合があります。
email String ユーザの連絡用Eメールアドレス。IDプロバイダによってはnullの場合があります。
emailVerified boolean emailプロパティのEメールアドレスが、確認済みであるか否かを表します。Email/Password IDプロバイダ以外は意味がないと思われます。
photoURL String ユーザがIDプロバイダに登録しているアイコン画像へのURIです。ユーザが画像を登録していない場合、nullだったり、『画像があるならば そのURI』でしかなくて有効な画像が得られない場合もあります。
providerData Object IDプロバイダから得られたデータのようです。内容はIDプロバイダごとに異なる可能性があります。
providerId String 常に"firebase"でした。ログインしたIDプロバイダを識別できる文字列かと期待したのですが、残念仕様です。
uid String ユーザを識別するためのUIDです。

 サーバへのリクエストは1回ではないでしょうから、変数userIdTokenにIDトークンを保存して、2回目以降のリクエストに備えています。
 firebase.auth.Auth.onAuthStateChanged()メソッドの代わりにfirebase.auth.Auth.onIdTokenChanged()メソッドも使えそうです。


サーバの実装.

Adminライブラリのリンク.

 Mavenプロジェクトなら、pom.xmlに以下を追加することでFirebase Adminライブラリがリンクされます。

    <!-- firebase -->
    <!-- https://mvnrepository.com/artifact/com.google.firebase/firebase-admin -->
    <dependency>
        <groupId>com.google.firebase</groupId>
        <artifactId>firebase-admin</artifactId>
        <version>6.12.2</version>
    </dependency>

ソース.

 サーバではAuthorizationヘッダで受け取ったIDトークンを検証し、リクエストがIdentity Platformで認識されているユーザによるものかを判断できます。サーバの実装には、Firebase Admin SDK(Admin Auth API)を使用します。「Verifying tokens on the server」のサンプルはNode.jsのみですが、Javaでもやることは同じです。以下はGAE/Jのサンプルです。エラー処理は省いてあります。

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.firebase.FirebaseApp;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseToken;

class SampleServlet extends HttpServlet {
    FirebaseAuth firebaseAuth;
    private FirebaseToken userIdToekn;
    ...
    public void init() {
        // FirebaseApp初期化、FirebaseAuth取得
        FirebaseApp.initializeApp();
        firebaseAuth = FirebaseAuth.getInstance();
    }

    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // IDトークンの検証
        verifyIdToken(request, response);

        ... リクエストエンドポイントの本来の機能を実行
    }
    ...
    protected void verifyIdToken(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // AuthorizationヘッダからIDトークンを取得
        String authheader = request.getHeader("authorization");
        String[] authvalues = authheader.split(" ");
        String rawtoken = authvalues[1];                    // IDトークン

        try {
            // IDトークンの検証
            userIdToken = firebaseAuth.verifyIdToken(rawtoken);

            // ユーザ情報の取得
            String displayname = userIdToken.getName();     // 表示名
            String email       = userIdToken.getEmail();    // 連絡用Eメールアドレス
            String uid         = userIdToken.getUid();      // UID
       } catch (FirebaseAuthException e) {
            // IDトークンが不正
            userIdToken = null;
            ... 401エラーを返す
            throw new IOException("適当なエラーメッセージ");
        }
    }

 最初にcom.google.firebase.FirebaseApp.initializeApp()メソッドでFirebase Admin SDKを初期化するのですが、このメソッドがIllegalStateExceptionを発生することがあります。既に起動されているGAEアプリのインスタンスが連続してリクエストを受け取ると、2回目以降でこの例外が起きるようです。そのため上記のサンプルではHttpServlet.init()メソッドをオーバーライドして、この中で実行して回避しています。


IDトークンの検証.

 IDトークンの検証にはcom.google.firebase.auth.FirebaseAuth.verifyIdToken()メソッドを使用します。Authorizationヘッダから取り出したIDトークンを渡して例外が発生しなければ、正しい手順によるリクエストと判断して良さそうです。


ユーザ情報の取得.

 FirebaseAuth.verifyIdToken()メソッドの戻り値のFirebaseTokenクラスは、IDトークンをデコードした結果であり、ここにユーザ情報が含まれています。
 Firebase Admin SDKのAPIリファレンスを見てもFirebaseTokenクラスについては簡単にしか説明がないので、各メソッドについて気付いた範囲で捕捉します。クライアント側で使用するfirebase.auth.Userクラスとほぼ同じ内容を取得できます。

メソッド 説明
getClaims() IDプロバイダから得られた情報が含まれます。内容はIDプロバイダごとに異なります。
getEmail() ユーザへの連絡用のEメールアドレスです。nullの可能性があるうえに、ユーザの操作により変更される可能性があるので、ユーザIDとしては使えません。
getIssuer() IDトークンの発行者を示していると思われます。実際の値は "https://securetoken.google.com/プロジェクトID" でした。
getName() ユーザの表示名です。IDプロバイダから渡されたものなので、Users APIのUser.getNickname()と違って、ちゃんと名前が返ってきます。
getPicture() ユーザがIDプロバイダに登録しているアイコン画像へのURIです。ユーザが画像を登録していない場合、nullだったり、『画像があるならば そのURI』でしかなくて有効な画像が得られない場合もあります。
getUid() Identity Platformが割り当てたユーザのIDです。このUIDが個々のユーザを識別するために使用できます。
isEmailVerified() getEmail()メソッドで得られるEメールアドレスが、確認済みであるか否かを返します。

 ユーザが登録済みであるか新規なのかを判断する材料は、ここにはありません。自身のGAEアプリに登録されていないUIDなら、新規登録と判断するしかないようです。


リクエストエンドポイントの実装.

 IDトークンの検証が成功し、正しいユーザからのリクエストであることが確認できたら、リクエストエンドポイントの本来の機能を実行します。ここは通常のGAEアプリの実装と同じです。


ログアウト.

ログアウト.

 ログアウトはfirebase.auth.Auth.signOut()メソッドを呼ぶだけです。結果はthen()で受け取れます。

function logoutUser() {
    event.preventDefault();

    firebase.auth().signOut().then(function() {
        // ログアウト完了
    });
}

再認証.追加 2020/06/10
修正 2021/05/01

概要.

 firebase.auth.User.delete()メソッドなど、一部のFirebase JavaScript SDKのAPIをコールする際には、『直近の認証成功から一定時間以内に実行』というセキュリティ上の制限があります。この制限を守らなかった場合、コールしたメソッドでauth/requires-recent-loginエラーが発生します。この制限に該当するメソッドは、エラーコードにauth/requires-recent-loginが含まれることでわかります。


 これらのメソッドはログイン直後でないと実行できないわけではなく、直前に再認証を行うことにより、実行可能になります。
 再認証は以下のメソッドにより実行できます。

クラス メソッド 対応するIDプロバイダ 説明
firebase.auth.User reauthenticateWithPopup() Email/Password・Phone IDプロバイダ以外 別ウィンドウ/タブで再認証を行います。
reauthenticateWithRedirect() Email/Password・Phone IDプロバイダ以外 再認証のためにURI遷移します。
reauthenticateWithPhoneNumber() Phone IDプロバイダ SMSによる認証を使用している場合に使用するメソッドのようです。
reauthenticateWithCredential() Email/Password IDプロバイダ Email/Password IDプロバイダの場合に使用する再認証のメソッドです。

実装.

 Email/Password IDプロバイダの場合は「パスワード認証の追加実装」の「再認証」を参照してください。

 Email/Password IDプロバイダおよびPhone IDプロバイダ以外の、OAuth系のIDプロバイダの実装について、ここで説明します。


reauthenticateWithPopup()メソッド.

 FirebaseのAPIリファレンスにも例が載っていますが、firebase.auth.User.reauthenticateWithPopup()メソッドによる再認証は、以下の様になります。再認証は、タブブラウザでない場合は別ウィンドウが開いて、タブブラウザの場合は別タブまたは別ウィンドウが開いて行われます。

var signinProvider = 'google';                 // Google IDプロバイダの場合
var authCredential;                            // クレデンシャルの保存用

function doReauthenticate() {
var provider = new firebase.auth.OAuthProvider(signinProvider); user.reauthenticateWithPopup(provider).then( function(result) { // 成功 authCredential = result.credential; // 一応クレデンシャルを保存してみた }, function(error) { // エラー処理 } ); }

 再認証した時点でユーザが無効化されていた場合、ユーザはログインしたままですがエラーになります。上記のerror引数には文字列"Error: The identity provider configuration is not found."でした。


reauthenticateWithRedirect()メソッド.

 なぜかこちらのメソッドは、FirebaseのAPIリファレンスに例は載っていません。firebase.auth.User.reauthenticateWithRedirect()メソッドによる再認証の開始は、以下の様になります。

var signinProvider = 'google.com';             // Google IDプロバイダの場合

// 再認証開始
function startReautheticate() {
    var provider = new firebase.auth.OAuthProvider(signinProvider);
    user.reauthenticateWithRedirect(provider);
}


 ページ遷移を伴うので、再認証の結果は遷移後のページでしか確認できません。以下その例です。

var authCredential;                            // クレデンシャルの保存用

// リダイレクト後に再認証結果を取得
function getReauthenticateResult() {
    var auth = firebase.auth();
    auth.getRedirectResult().then(
        function(result) {
            authCredential = result.credential;    // 一応クレデンシャルを保存してみた
        },
        function(error) {
            // エラー処理
        }
    );
}

 再認証した時点でユーザが無効化されていた場合、ユーザはログインしたままですが、auth/operation-not-allowedのエラーコードが返ってきます。


ユーザがログインに使用したIDプロバイダのインスタンス取得.

 上記のサンプルコードに示したとおり再認証のメソッドを呼ぶにあたっては、ユーザがログインに使用しているIDプロバイダのインスタンスを渡す必要があります。前記のサンプルソースでは変数providerに用意しています。
 このインスタンスの取得は、各IDプロバイダのクラスをコンストラクトするより、firebase.atuh.OAuthProviderクラスを使用するのが簡単です。コンストラクタの引数にIDプロバイダを識別する文字列を渡せば各IDプロバイダのインスタンスを作ってくれますので、条件分岐などは必要なくなります。


クライアント側でのIDプロバイダの判断.

 ユーザがログインに使用したIDプロバイダを識別する文字列の取得が必要になります。firebase.auth.Auth.onAuthStateChanged()メソッドで渡されるUser.providerIdプロパティがそうであればうれしいのですが、「IDトークンの取得とサーバへのリクエスト.」にも記載したとおり、残念ながらそのプロパティはなぜか"firebase"という文字列に固定されています。
 firebase.auth.IdTokenResult.signInProviderプロパティなら、期待通りにIDプロバイダを識別する文字列を取得できます。このクラスのインスタンスを取得するためには、firebase.auth.User.getIdToken()メソッドではなく、firebase.auth.User.getIdTokenResult()メソッドを使用します。このメソッドを使用して「IDトークンの取得とサーバへのリクエスト」のIDトークン取得のサンプルソースを書き換えると、以下の様になります。

<html>
  <body>
    <script type="text/javascript">
      var userIdToken;        // IDトークン保存用
      var signInProvider;     // ユーザがログインに使用したプロバイダの保存用

      ... Firebase JavaScript SDKの初期化
      ... 認証UIの実行

      firebase.auth().onAuthStateChanged(function(user) {
	  if (user) {
          // 任意 : ユーザの表示名を決定する。
          // user.displayNameが提供されるならそれを、提供されないなら代わりにuser.emailを使用しています。
          var dispname = user.displayName? user.displayName: user.email;
          ... disnameを必要なら適当に表示

          // IDトークンの取得
          user.getIdTokenResult().then(function(result) {
            // IDトークンはサーバへのリクエストのたびに必要になるので、保存。
            userIdToken = result.token;
            // ユーザがログインに使用したプロバイダの識別子を保存
            signInProvider = result.signInProvider;

            // GAEアプリに実装した、ログインがあったことをサーバに通知するエンドポイントをたたく
            $.ajax({
              type: 'GET',
              url: 'ログイン通知エンドポイントのURI',
              headers: {
                'Authorization': 'Bearer ' +userIdToken,        // AuthorizationヘッダでIDトークンを渡す
              },
            });
          });
        } else {
          // ログアウト
          userIdToken = null;        // IDトークンはもう無効なので破棄
          signInProvider = null;     // プロバイダも無効なので破棄
        }
      });
    </script>
  </body>
</html>

 ユーザがログインに使用したIDプロバイダを識別する文字列が取得できてしまえば、IDプロバイダのインスタンスは上記のサンプルコードに示したように、firebase.auth.OAuthProvider()コンストラクタで生成できます。FirebaseのAPIリファレンスのサンプルどおりです。


サーバ側でのIDプロバイダの判断.

 サーバ側の場合、Firebase Admin SDKのFirebaseToken.getClaims()メソッドで取得できるMapから、以下の様にIDプロバイダを識別する文字列が取得できました。

    protected String getLoginProviderId() {
        Map<String, Object> userclaim = userIdToken.getClaims();                               // IDトークンからクレームを取得
        if (userclaim != null) {
            Map<String, Object> firebase = (Map<String, Object>) userclaim.get("firebase");    // "firebase"を取得
            if (firebase != null) {
                String provider = (String) firebase.get("sign_in_provider");                   // さらに"sign_in_provider"を取得
                return provider;    // IDプロバイダを識別できる文字列
            }

            // プロバイダ不明
            return null;
        }

        // ユーザクレームなし
        return null;
    }

ユーザ管理.

 管理者によるユーザ管理には、Firebase Admin SDKを使用します。使えそうな機能は、多くはありません。APIリファレンスはこちら


ユーザ情報の取得.

 UIDもしくはEメールアドレスからユーザを検索し、そのユーザ情報を取得することができます。

UIDからの取得.

 FirebaseAuth.getUser()メソッドで、引数で渡したUIDにより指定したユーザの情報を取得できます。UIDで指定したユーザが見つかれば、そのユーザの情報が戻り値でUserRecordクラスで返されます。UIDがnullや空文字列の場合はIllegalArgumentExceptionが、指定のUIDに該当するユーザが見つからない場合はFirebaseAuthExceptionが発生します。


Eメールアドレスからの取得.

 FirebaseAuth.getUserByEmail()メソッドで、引数で渡したEメールアドレスにより指定したユーザの情報を取得できます。戻り値や例外は前述のFirebaseAuth.getUser()メソッドと同じです。


UserRecordクラスから得られる情報.

 UserRecordクラスではメソッドを使用してユーザの情報を取得することができます。APIリファレンスの説明は簡単なものでしかありません。使いそうなメソッドについて、実際使ってみて気付いたことも加えると、以下のとおり。またメソッド名は異なるものの、FirebaseTokenクラスのメソッドと同じ値が取得できるメソッドがあります。

メソッド名 説明 同じ値の取得できるFirebaseTokenメソッド
getDisplayName() IDプロバイダから渡された、ユーザの表示名です。 getName()
getEmail() ユーザへの連絡用のEメールアドレスです。nullの可能性があるうえに、ユーザの操作により変更される可能性があるので、ユーザIDとしては使えません。 getEmail()
getPhotoUrl() ユーザがIDプロバイダに登録しているアイコン画像へのURIです。ユーザが画像を登録していない場合、nullだったり、『画像があるならば そのURI』でしかなくて有効な画像が得られない場合もあります。 getPicture()
getProviderId() 常に"firebase"でした。firebase.auth.User.providerIdと同じく、これもログインしたIDプロバイダを識別できる文字列ではなく、残念仕様です。 -
getUid() Identity Platformが割り当てたユーザのIDです。このUIDが個々のユーザを識別するために使用できます。 getUid()
getUserMetadata() ユーザのメタデータが取得できます。含まれているのは、UserMetadataクラスの説明どおり、登録日時と最終ログイン日時です。 -
isEmailVerified() getEmail()メソッドで得られるEメールアドレスが、確認済みであるか否かを返します。 isEmailVerified()

ユーザ一覧.

 Identity Platformに登録されているユーザのリストが、FirebaseAuth.listUsers()メソッドで取得できます。最大1000名分まで、全てのユーザが順不同でリストされます。ソートやフィルタリングはできませんので、残念ながら検索などには使えません。単純にリストするだけです。検索したければアプリの方で実装することになります。


 単純に全ユーザ(ただし最大1000名)をリストするだけのコードは、下記の様になります。1000名以上を取得したい場合、ListUsersPage.getNextPage()メソッドを使うか、ListUsersPage.getNextPageToken()メソッドの返すトークンをFirebaseAuth.listUsers()メソッドに渡して、次のリストの取得を繰り返します。

    protected void listUsers(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setContentType("text/plain");
        response.setCharacterEncoding("UTF-8");
        Writer writer = response.getWriter();

        ListUsersPage listusers = firebaseAuth.listUsers(null);
        for (UserRecord userrecord: listusers.getValues()) {
            writer.write("UID=" +userrecord.getUid() +", name=" +userrecord.getDisplayName() +", Email=" +userrecord.getEmail() +"\n");
        }
    }

ユーザの削除.

クライアント側での実装.

 firebase.auth.User.delete()メソッドで、ログイン中のユーザをログアウトしたうえで、削除できます。
 ログインから一定時間の経過後にこのメソッドを呼ぶと、auth/requires-recent-loginコードのエラーが発生します。このエラーを回避するためには、firebase.auth.User.reauthenticateXxxx()メソッドのどれかを使用して再認証後に、このメソッドを呼ぶようにします。


サーバ側での実装.

 FirebaseAuth.deleteUser()メソッドで、引数で渡したUIDにより指定したユーザを削除できます。クライアント側で実行するfirebase.auth.User.delete()メソッドは認証から時間が経ってから実行するとauth/requires-recent-loginコードのエラーが発生しますが、このFirebaseAuth.deleteUser()メソッドはそのような制限はありません。ユーザのログインに関係なく、指定のユーザを強制的に削除できます。管理者権限で特定ユーザを追い出す際に使用できます。
 Identity Platformから削除するだけなので、アプリでもユーザ情報を保持しているなら、それはアプリで対処する必要があります。


ユーザ情報の編集.追加 2020/06/10

 ユーザ情報を編集する手段は、クライアント側のFirebase Javascript SDKのfirebase.auth.User.updateProfile()メソッド、サーバ側のFirebase Admin SDKのcom.google.firebase.auth.FirebaseAuth.updateUser()メソッド、これら2種類が用意されています。ここでは管理者による編集を対象としますので、サーバ側での実装のみ説明します。。


ユーザ情報の更新.

 com.google.firebase.auth.FirebaseAuth.updateUser()メソッドを使用して、Identity Platformに保存されているユーザの情報を、更新できます。引数のUserRecord.UpdateRequestは、UserRecord.updateRequest()メソッドで取得したものを編集すると、楽に用意できます。

    protected UserRecord updateUserRecord(HttpServletRequest request) throws IOException {
        // リクエストからパラメータを取得
        String uid = request.getParameter("uid");
        String dispname = request.getParameter("displayName");
        String email = request.getParameter("email");
        String disstr = request.getParameter("disabled");
        boolean disabled = ((disstr != null) && disstr.contentEquals("true"));
        String photourl = request.getParameter("photoUrl");

        try {
            UserRecord userrecord = firebaseAuth.getUser(uid);           // ログインユーザの情報を取得して
            UserRecord.UpdateRequest req = userrecord.updateRequest()    // UpdateRequestを編集
             .setDisplayName(isEmpty(dispname)? null: dispname)
             .setEmail(email)
             .setDisabled(disabled)
             .setPhotoUrl(isEmpty(photourl)? null: photourl);
            UserRecord updatedrecord = firebaseAuth.updateUser(req);     // ユーザ情報を更新

            // 必要なら変更後のUserRecordであるupdatedrecordを出力
        } catch (FirebaseAuthException e) {
            // エラー処理
        }
    }

 ユーザ情報は好きなように編集できるわけではなく、UserRecord.UpdateRequestクラスのメソッドには制限があります。使いそうなものだけ、取り上げてみます。

項目 メソッド 制限内容
カスタムクレーム setCustomClaims() なさそうです。
有効/無効 setDisabled() なさそうです。
表示名 setDisplayName() 空文字列にできません。表示名を無効にしたい場合は、nullを渡します。
Eメールアドレス setEmail() nullまたは空文字列にできません。
アイコン画像URI setPhotoUrl() 空文字列にできません。アイコンを無効にしたい場合は、nullを渡します。

 ユーザ情報を編集しても、IDプロバイダから提供された各プロバイダのアカウント情報が変更されるわけではありません。Firebase Admin SDKで変更されるのは、各プロバイダのアカウント情報をコピーして、Identity Platformが保持している情報です。


Eメールアドレスの変更について.

 Eメールアドレスを変更するうえでは、API仕様の矛盾があります。EメールアドレスはIDプロバイダによっては提供されない(=null)のケースがあります。com.google.firebase.auth.UserRecord.getEmail()メソッドにはその説明はありませんが、com.google.firebase.auth.UserInfo.getEmail()メソッドに説明されているとおりです。
 しかしcom.google.firebase.auth.UserRecord.UpdateRequest.setEmail()メソッドでは、Eメールを消したくても、nullや空文字列を渡すことができません。実際にnullや空文字列を渡してみると、下記の様に IllegalArgumentExceptionが発生しました。つまり一度Eメールアドレスが設定されてしまうと、それが間違いだったとしても、取り消す手段がありません。nullが設定できないのはバグのような気がします。

java.lang.IllegalArgumentException: email cannot be null or empty

 また他のアカウントで既に使われているEメールアドレスに変更しようとすると、下記の様に IOExceptionが発生します。

java.io.IOException: User management service responded with an error

ユーザの無効化.

 ユーザを一時的に無効化して、ログイン出来なくすることができます。再認証も出来なくなります。有効化すれば、再びログイン・再認証が出来るようになります。GCPコンソールの「IDプラットフォーム」-「ユーザー」で状態の確認および、状態の設定が可能です。
 無効化された状態のユーザでも、ログアウトは正常に可能です。
 実装は前節「ユーザ情報の更新.」で紹介したcom.google.firebase.auth.UserRecord.UpdateRequest.setDisabled()メソッドを使用します。ソースコードも前節を参照してください。


▲page top.
Copyright 2005-2024, yosshie.