GAミント至上主義

Web Monomaniacal Developer.

ブラウザでログインしたFirebaseのユーザー情報をサーバー側で取得する

前回からの引き続きやりたかったのは、Vue.jsとFirebase(ブラウザ上)でログイン済みユーザーのメールアドレスに、サーバー側からメールを送りたかった。

Vue.jsのPWAでバックエンドをDjangoからFirebaseに移行した - GAミント至上主義

もちろん、js側でFirebaseのユーザーのメールアドレスを取得して、サーバー側にPOSTなんかで一緒に送ればできる。

詳細は公式
Interface: User  |  Firebase


でもサーバー側ではそのメールアドレスが本当にそのユーザーのものなのかはわからず、でたらめなメールアドレスでも受け取ってしまうので危険なメール発射台にされてしまう可能性がある。
リクエストはユーザーのWEBブラウザから送られてくるのでIP制限もできない。

そのため、ブラウザのjs側からはtokenを送り、サーバー側でそのtokenを使ってユーザーのメールアドレスを取得して送信すれば、確実にユーザーのものに送ることができる。

今回Googleログインのみを使っているため、そのアクセストークンを取得し、GoogleのOAuthのAPIサーバー側に送ればいいかと思ったけど、そのトークンを取得できるのは下記のようなサインイン時のメソッドしか見当たらなかった。

サインイン直後なら使えるけど、期限が1時間で切れてしまうが、それを更新する方法が再ログインさせるしか見当たらず、ユーザー側の流れが切れてしまうし、リダイレクトを挟んだり処理が複雑になるので避けたい。

firebase.auth().signInWithPopup(provider).then(function(result) {
  // This gives you a Google Access Token. You can use it to access the Google API.
  var token = result.credential.accessToken;
  // The signed-in user info.
  var user = result.user;
  // ...
})

で、しばらくどうしようと思ってたけど、わざわざGoogleのOAuth APIからではなく、FirebaseのAPIから直接取ればいいと気づいた。

ログイン完了後は公式通りonAuthStateChangedでユーザーオブジェクトを取得できるので、グローバルなプロパティか、vuexなどに入れておくと便利。

Firebase でユーザーを管理する  |  Firebase

firebase.auth().onAuthStateChanged(function(user) {
  if (user) {
    // User is signed in.
  } else {
    // No user is signed in.
  }
});

Firebase Auth REST API  |  Firebase

Firebaseのトークンは公式通りgetIdTokenで取得する。forceRefresh は付けといた方がいいのかな?

ID トークンを確認する  |  Firebase

firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then(function(idToken) {
  // Send token to your backend via HTTPS
  // ...
}).catch(function(error) {
  // Handle error
});


メールアドレスなどのユーザー情報を取得するエンドポイントは下記でfirebaseのAPI_KEYが必要。これは初期導入時にゲットしているはず。

https://www.googleapis.com/identitytoolkit/v3/relyingparty/getAccountInfo?key=[API_KEY]

このエンドポイントにサーバー側にAPI_KEYと、トークンを渡せば取得できそう。
有効期限はあるもののパスワードと同レベルの情報なので、HTTPSでPOSTで送らないとセキュリティ上まずいはず。


Python側(DjangoのView内)で、試しにVue側から送られてきたAPI_KEYとトークンを使って
ユーザー情報を取得する下記のような関数を作って動作確認

API_KEYはリクエストURLに含めるので正規表現でバリデーションか、エンコード等した方がよさげ。

import requests
import json


def get_userinfo(apikey, token):
    resp = requests.post('https://www.googleapis.com/identitytoolkit/v3/'
                                      'relyingparty/getAccountInfo'
                                      '?key={}'.format(apikey),
                                      params={'idToken': token})
    logger.debug("userinfo:{}".format(resp.text))
    return json.loads(resp.text)

logger.debugで下記のようなJSONで返ってくる

2018-05-17 12:24:07,080 [DEBUG] shanyang.views: userinfo:{
  "kind": "identitytoolkit#GetAccountInfoResponse",
  "users": [
    {
      "localId": "xxxxxxxxxxxxxxxxxxxxxxxxxx",
      "email": "xxxxxxxxxxxxx@example.co.jp",
      "displayName": "なまえ",
      "photoUrl": "https://lh3.googleusercontent.com/xxxxxxxxxxxxxxxxxxxxxxxxxxx/photo.jpg",
      "emailVerified": true,
      "providerUserInfo": [
        {
          "providerId": "google.com",
          "displayName": "なまえ",
          "photoUrl": "https://lh3.googleusercontent.com/xxxxxxxxxxxxxxxxxxxxxxxxxxx/photo.jpg",
          "federatedId": "000000000000000000",
          "email": "yu_yamazaki@bizocean.co.jp",
          "rawId": "xxxxxxxxxxxxx@example.co.jp"
        }
      ],
      "validSince": "1525917073",
      "lastLoginAt": "1526362431000",
      "createdAt": "1525917073000"
    }
  ]
}

上記の関数でjson.loadsまでやっているので、下記のような感じでemailを取得できるとこまで確認。
失敗したときの例外処理がもちろん必要になる。

user_info = get_userinfo(apikey, token)
user_email = user_info['users'][0]['email']

これでFirebaseで認証済みユーザーの情報をサーバー側で取得することができた。