LoginSignup
27
28

More than 5 years have passed since last update.

PythonからFirebase認証を利用する(メール/パスワード認証編)

Last updated at Posted at 2019-04-10

絶賛 Firebase 調査中です。
これまで 「Vue.js の主要な機能をざっくりとつかってみたときのメモ(Firebase認証・認可編)」 では、Webアプリ(つまりフロントエンド側)をFirebase認証で保護する方法を整理しました。
つづいて「Cloud Functions で構築したREST API、自前RESTサーバのAPIにFirebase認証を適用する。」 こちらでは、FirebaseのFunctionsや自前のRESTサーバ(つまりバックエンド側)をFirebase認証で保護する方法を整理しました。

さて今回は、普通のアプリ(つまりWeb系じゃないフロントエンド側)をFirebase認証で保護する方法です。Pythonでアプリを開発してて、さくっと認証したいってニーズがあり、Firebaseの「メール/パスワード認証」を使えるかな?と試したときのメモです。

結論いうと、簡単かつ、とてもつかいやすいです :-)

ちなみに、メール/パスワード認証だけでなく、Firebase経由のOAuth認可機構なども、利用できることを確認してるんですが、それはまたあらためて別に書くことにします。

前提の環境

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.14.4 (Mojaveです)
BuildVersion:   18E226

$ python --version
Python 3.7.1

Pythonのお手前にのっとって、分離環境をつくります。

$ python3 -m venv ~/venv/venv_fb
$ source ~/venv/venv_fb/bin/activate
(venv_fb) $ which python3
/Users/xxx/venv/venv_fb/bin/python3
(venv_fb) $ 

アプリ側の環境構築は以上です。

Firebase認証(メール/パスワード認証)を有効にする

Firebase プロジェクトの構築はいろんなヒトが記事にしているので、そこは省略。。
いわゆる

  var config = {
    apiKey: "## FIREBASE API KEY ###",
    authDomain: "### FIREBASE AUTH DOMAIN ###",
    databaseURL: "https://##PROJECT ID##.firebaseio.com",
    projectId: "### CLOUD FIRESTORE PROJECT ID ###",
    storageBucket: "##PROJECT ID##.appspot.com",
    messagingSenderId: "YOUR-SENDER-ID"
  };

とかまでは、できてる前提ですすめます。

さて認証機能のセットアップです。
Firebaseのコンソールから、自分が作成したプロジェクトを選択し、左のメニュー部の「Authentication」を選択。
image.png

「ログイン方法」を選択すると、下記のようにログイン方法を選択する画面になります。今回は「メール/パスワード認証」をつかうので、メール/パスワードを「有効」にしておきましょう。

image.png

さてメール/パスワード認証するためのユーザを作っておきます。

アドレス パスワード
test001@example.com hogehogehoge

image.png

Firebase側の準備も以上です。

やってみる

さて早速やってみますが Firebase Authentication のサイトを見ると、認証のSDKはPython版が存在しないようですね。なので、Firebase Auth REST API にあるREST APIを直接コールすることになります。

まあ REST APIのインタフェース見ながら値をセットすればOK1です。。

あ、じつはこのへんみると、サードパーティのRESTのWrapperがあったりするんですが、今回はシンプルに直接コールしてみたいとおもいます。

コード

下記の通りにコードを準備します。

config/config.ini
(venv_fb) $ cat config/config.ini
[firebase_config]
  api_key = ## FIREBASE API KEY ###  // Firebaseの apiKey

[proxy]
  proxy = no
  http = http://127.0.0.0:8888
  https = http://127.0.0.0:8888
(venv_fb) $ 
index.py
(venv_fb) $ cat index.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import json
import requests
import configparser

def main():
    config = configparser.ConfigParser()
    config.read("./config/config.ini")

    api_key = config["firebase_config"]["api_key"]

    email = 'test001@example.com'
    password = 'hogehogehoge'

    user = sign_in_with_email_and_password(api_key, email, password, config)

    print('---- sign_in_with_email_and_password -----')
    print_pretty(user)
    # print(user['idToken'])


def sign_in_with_email_and_password(api_key, email, password, config):
    """
    Firebaseで認証を行う(SDKの signInWithEmailAndPassword と同値)
    :param api_key:
    :param email:
    :param password:
    :return: Firebaseの、idTokenなどを含んだ、認証情報
    """
    # https://firebase.google.com/docs/reference/rest/auth/#section-sign-in-email-password
    uri = f"https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key={api_key}"
    headers = {"Content-type": "application/json"}
    data = json.dumps({"email": email, "password": password, "returnSecureToken": True})
    proxies, verify = get_proxy(config)

    result = requests.post(url=uri,
                           headers=headers,
                           data=data,
                           proxies=proxies,
                           verify=verify)
    return result.json()


def get_proxy(config):
    verify = True
    proxies = None

    if config["proxy"].getboolean("proxy"):
        proxies = {
            "http": config["proxy"]["http"],
            "https": config["proxy"]["https"]
        }
        verify = False

    return proxies, verify

def print_pretty(obj):
    print(json.dumps(obj, ensure_ascii=False, indent=4, sort_keys=True, separators=(',', ': ')))


if __name__ == '__main__':
    main()

電文の中身をチェックするためにproxyをかませられるようにしてあったりしますが、RESTのAPIをよんでるだけのシンプルなコードです。

実行する

コードは requests ライブラリを使用しているので、pip でインストールしつつ、実行してみます。

(venv_fb)$ pip install requests
...
(venv_fb)$
(venv_fb)$ python3 index.py

---- sign_in_with_email_and_password -----
{
    "displayName": "",
    "email": "test001@example.com",
    "expiresIn": "3600",
    "idToken": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjdkMmY5ZjNmYjgzZDYzMzc0OTdiNmY3Y2QyY2ZmNGRmYTVjMmU4YjgiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20veHh4eHgteHh4eHgiLCJhdWQiOiJ4eHh4eC14eHh4eCIsImF1dGhfdGltZSI6MTU1NDgxMjAxMSwidXNlcl9pZCI6IlV2M2ZuNkpSdHljM1pid05XYXh4eHh4eHgiLCJzdWIiOiJVdjNmbjZKUnR5YzNaYndOV2F4eHh4eHh4IiwiaWF0IjoxNTU0ODEyMDExLCJleHAiOjE1NTQ4MTU2MTEsImVtYWlsIjoidGVzdDAwMUBleGFtcGxlLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJlbWFpbCI6WyJ0ZXN0MDAxQGV4YW1wbGUuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0K.wEa67EK-zVLwERkkxiXpkJjH5pHsRlOv4qC74R9ICh25I0oEXHGBFfnYvL2HPPYKP3X5YF-wL-FYjejPa138bjoAJ57hfRgW-5Gceu0GBNeDPIiim-jifNQ3K4AQzqak9WbUYChNPnzjW_vuRUscKAgTZIMz6ozYmv3UC-whDhKcBP8IMqVGKXlX2Z6-4X4eEfPQ2-gKQ_fiXhpEO9sTRV3KOahCKtkpFrtdoHCcSpOeoATdb-Z_h71FXVi-R6oiVG5F5AAObUVY0nhGqZfzCZl6WG2J8Fl5UG5_7oaTFIcGFE2jyLqvnph63buGRsLU0rMku8kX1p3f4v8JPWLWpw",
    "kind": "identitytoolkit#VerifyPasswordResponse",
    "localId": "Uv3fn6JRtyc3ZbwNWaAjxXowkwc2",
    "refreshToken": "AEu4IL0tZ5IsoBGlqHaw8Ll_5IQ5vcY_sEAYDqecAD7dbBR7oBYp93oCG-z_ajhcNWlJtwAc52Ub5vhckWBT6VyS6qSOSs0z403qKlw8YH4UtiGZ9-aXuOT8VT4n8_B26hTOl_YBQPgG9ZqPSbPDv3yuy78ZZQVP0syLVuQLGJxJzbB9YzogGQRSgDPrd2etIHkA4tignV99oR8eAIdQFAuNhpEmjWkm-Q",
    "registered": true
}
(venv_fb)$

何やら結果が表示されましたが、上記の形式の戻り電文が返ってくれば、認証OKといえそうです。ちなみにわざとパスワードを間違えてみると、

{
    "error": {
        "code": 400,
        "errors": [
            {
                "domain": "global",
                "message": "INVALID_PASSWORD",
                "reason": "invalid"
            }
        ],
        "message": "INVALID_PASSWORD"
    }
}

という電文が返ってきました。詳細のインタフェース仕様は Firebase Auth REST API をご参照ください。

ログイン認証するだけでよければ、メール/パスワードによるFirebase認証は以上です。おつかれさまでした。

戻り値に含まれる idToken について

さて認証するだけであれば以上で完了ですが、対向する自前のRESTサーバのAPIなどもおなじくFirebase認証で保護したい場合は、戻り電文に含まれているidTokenを用いることになるので、それをもうすこしだけみてみます。

ちなみにidTokenとは、上記の

eyJhbGciOiJSUzI1NiIsImtpZCI6IjdkMmY5ZjNmYjgzZDYzMzc0OTdiNmY3Y2QyY2ZmNGRmYTVjMmU4YjgiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20veHh4eHgteHh4eHgiLCJhdWQiOiJ4eHh4eC14eHh4eCIsImF1dGhfdGltZSI6MTU1NDgxMjAxMSwidXNlcl9pZCI6IlV2M2ZuNkpSdHljM1pid05XYXh4eHh4eHgiLCJzdWIiOiJVdjNmbjZKUnR5YzNaYndOV2F4eHh4eHh4IiwiaWF0IjoxNTU0ODEyMDExLCJleHAiOjE1NTQ4MTU2MTEsImVtYWlsIjoidGVzdDAwMUBleGFtcGxlLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJlbWFpbCI6WyJ0ZXN0MDAxQGV4YW1wbGUuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0K.wEa67EK-zVLwERkkxiXpkJjH5pHsRlOv4qC74R9ICh25I0oEXHGBFfnYvL2HPPYKP3X5YF-wL-FYjejPa138bjoAJ57hfRgW-5Gceu0GBNeDPIiim-jifNQ3K4AQzqak9WbUYChNPnzjW_vuRUscKAgTZIMz6ozYmv3UC-whDhKcBP8IMqVGKXlX2Z6-4X4eEfPQ2-gKQ_fiXhpEO9sTRV3KOahCKtkpFrtdoHCcSpOeoATdb-Z_h71FXVi-R6oiVG5F5AAObUVY0nhGqZfzCZl6WG2J8Fl5UG5_7oaTFIcGFE2jyLqvnph63buGRsLU0rMku8kX1p3f4v8JPWLWpw

のことです。

idTokenをほどいて中身を見てみる

このidTokenは 前の記事で調べたとおりJWT(JSON Web Tokens) で符号化されているので、ドットで区切ってbase64で戻してみます。

ドットで区切ったうちの、まずひとつめ。

(ココだけlinuxで叩いてます。微妙にオプションとか違うかも)
$ echo eyJhbGciOiJSUzI1NiIsImtpZCI6IjdkMmY5ZjNmYjgzZDYzMzc0OTdiNmY3Y2QyY2ZmNGRmYTVjMmU4YjgiLCJ0eXAiOiJKV1QifQ | base64 -d  | jq
{
  "alg": "RS256",
  "kid": "7d2f9f3fb83d6337497b6f7cd2cff4dfa5c2e8b8",
  "typ": "JWT"
}

なんだかJWTとか鍵のアルゴリズムRS256とかが書いてあります。そしてふたつめはこちら。

(ココだけlinuxで叩いてます。微妙にオプションとか違うかも)
$ echo eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20veHh4eHgteHh4eHgiLCJhdWQiOiJ4eHh4eC14eHh4eCIsImF1dGhfdGltZSI6MTU1NDgxMjAxMSwidXNlcl9pZCI6IlV2M2ZuNkpSdHljM1pid05XYXh4eHh4eHgiLCJzdWIiOiJVdjNmbjZKUnR5YzNaYndOV2F4eHh4eHh4IiwiaWF0IjoxNTU0ODEyMDExLCJleHAiOjE1NTQ4MTU2MTEsImVtYWlsIjoidGVzdDAwMUBleGFtcGxlLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJlbWFpbCI6WyJ0ZXN0MDAxQGV4YW1wbGUuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0K | base64 -d | jq
{
  "iss": "https://securetoken.google.com/xxxxx-xxxxx",
  "aud": "xxxxx-xxxxx",
  "auth_time": 1554812011,
  "user_id": "Uv3fn6JRtyc3ZbwNWaxxxxxxx",
  "sub": "Uv3fn6JRtyc3ZbwNWaxxxxxxx",
  "iat": 1554812011,
  "exp": 1554815611,
  "email": "test001@example.com",
  "email_verified": false,
  "firebase": {
    "identities": {
      "email": [
        "test001@example.com"
      ]
    },
    "sign_in_provider": "password"
  }
}

このように、いろいろ取り出すことができました。このidTokenは、JWTの署名によって改ざんされていないことがチェックできたり、

  • aud(Audience) の値で、発行したFirebaseプロジェクトの値を確認
  • exp(Expiration time)の値によって、有効期限が切れたトークンでないかを確認

などができます。したがってこのidTokenを自前のRESTサーバに送ることで、APIをFirebase認証で保護するように作り込むことができるわけですね。

その辺の詳細は、下記に整理しましたのでご参考にどうぞ。

参考: RESTサーバから、トークンの正当性チェックを行いたい

まとめ

メール/パスワード認証による、普通のアプリ(つまりWeb系じゃないフロントエンド)の認証機能の実装は以上です。ひきつづき、普通のアプリをFirebase経由のOAuthをつかって保護する方法をまとめてみたいと思います。色々調べてみた感じだと、OAuthの処理シーケンスがブラウザのリダイレクト前提(いわゆるOAuth Dance!) なため、ローカルでWEBサーバを立ち上げて認可コードがリダイレクトされてくるのを待つ、みたいなやり方でいちおう実現できそうです、、。このへんはこちらのかたの情報を参考にさせてもらいました。

PythonでCLIからOAuth2を利用してQiitaのアクセストークンを取得してみた

あらためて、記事にしてみます。

おつかれさまでした。

関連リンク


  1. 電文を見てみると他の言語のSDKも、おなじRESTをコールしてるだけっぽいですね。 

27
28
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
28