JavaScript
Facebook
OAuth
googleapi
Firebase

FirebaseのWeb版OAuth認証(Google, Facebookなど)で、どの方法を選んでも「メールアドレス」で一意にログイン判定したい

導入

Firebase便利ですね。便利ですがちょっと値付けがお高い感じもします。
そんな状況の中で使い所が限られてくるわけですが、色々いじっているとZapierでPaypal連携してサービスの購入・購読情報をRealtimeDatabaseで管理しつつ、Firebaseの認証(OAuth)と組み合わせてサービスの制限・開放をコントロールする分にはRealtimeDatabaseの容量なども嵩まず便利に使えるのではないかと考えました。

複数のOAuth利用時の課題

ところが、

  • Paypal決済で取得できる情報でOAuthと突き合わせできそうな情報はメールアドレスくらい
  • OAuthの種別が異なると、Firebaseは異なるユーザーとして認識してしまう

という事がわかってきました。
OAuthの設定で、同じメールアドレスを持ったOAuthを弾くか受入れるかの指定は出来るのですが、各OAuthに紐付いたメールアドレスが同一の場合はFirebase上のIDとしても同一と捉えるということが出来ないのです。

2017-10-12_103529.gif

Google, Facebook, Twitterで認証するのはQiitaの記事など見なくてもFirebaseのヘルプや自動javascript生成機能であっという間に実装できてしまうのですが、「メアドが同一であれば異なるOAuth種別の登録も同一IDとみなす」実装方法はどこにも無さそうです。
StackOverflowには「メアドで統一認識するのがベストだよね」といった意見はあるものの具体的な方法は見あたりません。

Google認証の説明
Facebook認証の説明

実現したいことの主旨

どんな認証方法でログインしても同じメアドなら、同じデータ(RealtimeDatabase, Storage)にアクセスできるように実装をしたいというのが趣旨です。

JSON構造のキーをメアドにすることでルールを組み立てられないか

JSONの構造のUIDキーをメアドにする方法が思い浮かびますが、以下のような課題があり難しそうです。

  • メアドそのままだと.(ドット)が邪魔してエラーになるので、リプレースしないといけない
  • 複雑な操作(MD5とか)が提供されていないので生のメアドを扱う必要がある ※ メアドのマッチングには正規表現やドットリプレースなど可能なようです。
ルールで正規表現を使う
".write": "auth.token.email_verified == true && auth.token.email.matches(/.*@gmail.com$/)"

RealtimeDatabaseのルール設定で使える関数

データ側の子にメアド情報を持たせてルールを組み立てられないか

  • この方法だと、キーではなく値側にメアドをそのまま入れられるのでルールの属性値と突合できそう

そもそも認証時のアドレス情報は二つある

認証してOKという紹介記事も多いのですが、やってみて分かる人には分かるとおりメアド情報が2箇所あります。
2017-10-12_120916.gif

認証時に渡ってくるユーザー情報をJSON.stringifyしたものです。
Firebaseが管理するメアド情報と、OAuthから取得したメアド情報(この場合、FBから提供された情報)の2つあることがわかります。
user.emailとするとこの場合nullになってしまうので、user.provideData[0].emailも見てあげなくてはいけません。

メアドベリファイ機能の問題

メールのベリファイをクリアすると、user.emailに取り込まれるものと思われます。しかし、FacebookでsendEmailVerification()メソッドを発行すると

メアド情報が見つからない的なエラー
Error: {"error":{"errors":[{"domain":"global","reason":"invalid","message":"MISSING_EMAIL"}],"code":400,"message":"MISSING_EMAIL"}}

というエラーが出ます。残念。Googleでは正常に動作しましたが、Facebookでは利用できない仕様なのかも知れません。

複数のメアドが登録されていた場合

Facebookには複数のメアドを登録出来ます。これを登録した場合の認証時情報の差異について確認しました。

  • 新たにメアドを追加 → 何も変わらず(載ってこない)
  • 新しく追加したメアドの認証(Facebook)をした → 何も変わらず(載ってこない)
  • メインのアドレスを新しく追加したアドレスに変更 → 何も変わらず(載ってこない)
  • 古いアドレスを削除 → 何も変わらず(載ってこない)

ここまで変わらないのも何かおかしいので、FirebaseのAuthenticationを見に行ったところ管理コンソール上において「ユーザーの読み込み中に不明なエラーが発生しました」でユーザーリストが一切表示されなくなってしまいました。Firebase自体がFacebook メールアドレスの変更に対応出来ていない可能性があります。

(追記)もう一回、同じ操作をしてみました。今度は、Firebaseコンソールでのエラーは出なかったのですが、新しいメールアドレスがFacebookのアカウントに反映されませんでした。この状況を見ると、メアド変更に追随できないという事になりそうです。

本当は用意されていたマルチアカウントリンク機能

不勉強で申し訳なかったのですが、ちゃんとFirebase側でマルチアカウントのリンクという機能が用意されておりました。
JavaScript を使用してアカウントに複数の認証プロバイダをリンクする

こちらを見ると、GoogleでログインしてもFacebookでログインしても同じFirebaseユーザーとして認識できるようです。ただ、リンクするというアクションをJavaScriptで明示的に行う必要があるので、以下のようなワークフローで実装するのが良さそうです。

  • ある認証タイプでログインを試みる(ログインボタンなど)
  • 既に同じメアドの別認証タイプで登録があるとエラーになるので、既存アカウントでログイン後にリンクをするように促す

この方法ですと、例えメルアドが異なるアカウントであっても既存のFirebaseアカウントに統合することが出来ました。その際のFirebase上の識別子は最初に登録された情報(メルアド)が一意に採用されますので要件としては満たせそうです。

結論(現状)

linkWithPopupを上手に実装すれば容易に実装が出来る。

現在ログイン中のアカウントにFacebookアカウントを統合(メルアドが違っても)
$("#linkButtonFB").click(function(){
    var provider = new firebase.auth.FacebookAuthProvider();
    g_user.linkWithPopup(provider).then(function(result) {
        setMessage("Facebookアカウントのリンクに成功しました。Facebookアカウントでログインできるようになりました");
    }).catch(function(error) {
        setMessage("Facebookアカウントのリンク中にエラーが発生しました");
    });
});

g_userは現在ログイン中のFirebaseのユーザーインスタンスです。

2017-10-12_210735.gif

こんな感じで1つのメルアド(最初に登録してログイン可能なアカウント)にFacebookアカウントが統合され、Facebookアカウントで自前のサービス(Firebase)にもログインできるようになりました。