絶賛 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」を選択。
「ログイン方法」を選択すると、下記のようにログイン方法を選択する画面になります。今回は「メール/パスワード認証」をつかうので、メール/パスワードを「有効」にしておきましょう。
さてメール/パスワード認証するためのユーザを作っておきます。
アドレス | パスワード |
---|---|
test001@example.com | hogehogehoge |
Firebase側の準備も以上です。
やってみる
さて早速やってみますが Firebase Authentication のサイトを見ると、認証のSDKはPython版が存在しないようですね。なので、Firebase Auth REST API にあるREST APIを直接コールすることになります。
まあ REST APIのインタフェース見ながら値をセットすればOK1です。。
あ、じつはこのへんみると、サードパーティのRESTのWrapperがあったりするんですが、今回はシンプルに直接コールしてみたいとおもいます。
コード
下記の通りにコードを準備します。
(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) $
(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で戻してみます。
ドットで区切ったうちの、まずひとつめ。
$ echo eyJhbGciOiJSUzI1NiIsImtpZCI6IjdkMmY5ZjNmYjgzZDYzMzc0OTdiNmY3Y2QyY2ZmNGRmYTVjMmU4YjgiLCJ0eXAiOiJKV1QifQ | base64 -d | jq
{
"alg": "RS256",
"kid": "7d2f9f3fb83d6337497b6f7cd2cff4dfa5c2e8b8",
"typ": "JWT"
}
なんだかJWTとか鍵のアルゴリズムRS256とかが書いてあります。そしてふたつめはこちら。
$ 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のアクセストークンを取得してみた
あらためて、記事にしてみます。
おつかれさまでした。
関連リンク
- Vue.js の主要な機能をざっくりとつかってみたときのメモ(Firebase認証・認可編) WEBフロントエンド側をFirebase認証で保護する方法を整理
- Cloud Functions で構築したREST APIをFirebase認証で保護する。そして自前RESTサーバのAPIにもFirebase認証を適用する。 バックエンド側をFirebase認証で保護する方法を整理
- PythonでCLIからOAuth2を利用してQiitaのアクセストークンを取得してみた PythonからOAuth/OIDC をつかう方法で参考にさせてもらいました。
- Authleteを使った認可サーバの構築手順(CIBA対応版) CIBAが導入されて、リダイレクトなOAuth Danceがなくなれば、ローカルでWEBサーバを立ち上げるとか不要になるかな。。(Pythonでポーリングするだけになれば楽)
- JWT(JSON Web Tokens) JWT公式
-
電文を見てみると他の言語のSDKも、おなじRESTをコールしてるだけっぽいですね。 ↩