【第2回】Responder + Firestore でモダンかつサーバーレスなブログシステムを作ってみた【Firebase設定と認証機能実装】
2021.12.20
第2回~モダンなフレームワークの使い方を学びながらブログシステムを構築~
本記事は、「Python Responder + Firestore で、モダンかつサーバーレスなブログシステムを作ってみる」第2回目です。
前回は、初期設定として「Responder のセットアップ」まで解説しました。
今回は、「Firestore のセットアップ」を中心に解説していきます。
Firestore セットアップ
Firestore セットアップの手順
Firestore セットアップの大まかな流れは、以下の通りです。
- プロジェクトを作成する
- データベースを作成する
- サーバを初期化する
- 動作確認
公式ドキュメントも参照してみてください。
【Cloud Firestore を使ってみる】
https://firebase.google.com/docs/firestore/quickstart?hl=ja#python
プロジェクト作成
まずは、Firebase でプロジェクトを生成します。
「Firebase コンソール」から、「プロジェクトを作成」しましょう!
今回は、仮で「resp-fire-blog」としました。
無事にプロジェクトが生成できれば、以下の様な管理画面になるはずです。
データベース作成
そうしたら、「Cloud Firestore」にアクセスし「データベースの作成」をクリック。
「テストモードで開始」にしておきましょう。
これで、データベース作成完了です。
サーバの初期化
最初に、「Cloud Firestore」の初期化を部分実装します。
新しい秘密鍵の生成
「歯車マーク > プロジェクトを管理 > サービス アカウント」で、新しい秘密鍵の生成を行います。
json ファイルの保存を求められるので、新たに「/private」を作成し、保存します。
今回は、「keys.json」という名前にしました。
ファイルを作成
その後、以下のコードを書いたファイルを作成します。
ここでは、 init_db.py とします。
1 2 3 4 5 6 7 8 9 | import firebase_admin from firebase_admin import credentials from firebase_admin import firestore # Use a service account cred = credentials.Certificate('private/keys.json') firebase_admin.initialize_app(cred) db = firestore.client() |
このファイルを実行しても何も表示されませんが、これでサーバ準備は完了です。
注意!
ここで使った json ファイルは、絶対に外部に漏らしてはいけません。
Github にあげたりする場合は、これらのファイルは外してください。
本番サーバに上げる場合も、private ディレクトリは .htaccess でアクセスできないようにしておきましょう。
動作確認
データを追加
それでは、実際に、データを追加してみましょう。
Firestore では、Python ファイルからもデータを追加できます。
以下のコードを参照してください。
1 2 3 4 5 6 7 8 | from init_db import db doc_ref = db.collection('users').document('TTanaka') doc_ref.set({ 'first': 'Taro', 'last': 'Tanaka', 'born': 1994 }) |
これを実行してみると、以下のようにデータが追加できていることがわかります。
ここでは、コレクションは「モデル」、ドキュメントは「ID(主キー)」にあたります。
データの取り出し
データの取り出しは、以下のようにできます。
1 2 3 4 5 6 7 | from init_db import db users_ref = db.collection('users') docs = users_ref.stream() for doc in docs: print('{} => {}'.format(doc.id, doc.to_dict())) |
1 | TTanaka => {'first': 'Taro', 'last': 'Tanaka', 'born': 1994} |
とても直感的な操作が可能ですね。
本連載では、これを使ってブログシステムを構築していきます。
管理者ページを作る
最初に「管理者」を作り、「管理者ページ」と「ログインページ」を作っていきます。
Firebase Authenticationを使う
今回は、Firebase が提供する認証システム「Firebase Authentication」を使用します。
まずは、管理画面の「Authentication > Sign-in method」より、メールを選んで有効にしてください。
次に、ユーザを追加します。
GUI でユーザ追加はしない
「Authentication > Users」からユーザを追加できますが、今回この方法は行いません。
ディスプレイネーム(表示名)も設定したいからです。
GUI 操作では、ディスプレイネームは変更できないのです。
Firebase Auth RestAPI を使う
このような処理を Python から行いたい場合は、「Firebase Auth RestAPI」を使います。
これは、指定された URL へデータを POST することで、「データの作成」「更新」「認証」まで出来る API です。
【Firebase Auth RestAPI】
https://firebase.google.com/docs/reference/rest/auth/
Userを追加する
そうしたら、 auth.py を作り、サインアップ関数を作ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | import requests import json def _sign_up(email, password, displayName): # Api key取得 with open('private/apikey.txt') as f: api_key = f.read() # 管理者ユーザ追加 url = 'https://identitytoolkit.googleapis.com/v1/accounts:signUp?key={}'.format(api_key) headers = {'Content-type': 'application/json'} data = json.dumps({'email': email, 'password': password, 'returnSecureToken': True}) result = requests.post(url=url, headers=headers, data=data, ) if 'error' in result.json(): print('error: {}'.format(result.json()['error']['errors'][0]['message'])) return result.json() # DisplayNameを更新 url = 'https://identitytoolkit.googleapis.com/v1/accounts:update?key={}'.format(api_key) data = json.dumps({ 'idToken': result.json()['idToken'], 'displayName': displayName }) result = requests.post(url=url, headers=headers, data=data, ) if 'error' in result.json(): print('error: {}'.format(result.json()['error']['errors'][0]['message'])) return result.json() return result.json() if __name__ == '__main__': _sign_up('contact@sample.com', 'password', 'rightcode') |
複雑に見えますが、やっていることは「WebAPI に json を投げている」だけです。
apikey.txt を /private ディレクトリに用意
ここで、API Keyを記述した apikey.txt を「/private」ディレクトリに用意しておいてください。
「プロジェクトの設定」で確認できます。
実行
それでは、実行してみましょう。
そうすると、以下の様にユーザが追加できていることがわかります。
管理者ページとログイン画面を作る
認証機能を実装する前に、先に「管理者ページ」と「ログイン画面」を仮で作っておきます。
管理者ページ
「templates/admin.html」を作り、以下のように記述します。
1 2 3 4 5 6 7 8 9 10 11 12 13 | <!-- templates/admin.html 管理者ページ --> {% extends "layout.html" %} {% block content %} <br> <h1>My Blog Name | 管理者ページ</h1> <p>こんにちは,{{ name }} さん</p> {% endblock %} |
ひとまず、管理者ページはこれだけです。
ログインページ
次にログインページですが、今回は以下の様にしました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <!-- templates/login.html ログインページ --> {% extends "layout.html" %} {% block content %} <br> <h1>My Blog Name | ログイン</h1> <br> <div class="card login"> <form action="/admin" method="post"> <p>Email</p> <input name="email" type="email"> <p>Password</p> <input name="password" type="password"> <p class="error">{{ error }}</p> <button type="submit">Login</button> </form> </div> {% endblock %} |
CSSも追記します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | .card{ max-width: 500px; height: auto; margin: auto; } .login{ overflow: hidden; border-radius: 8px; box-shadow: 0 4px 15px rgba(0,0,0,.2); } .login form{ margin: 50px 10px; } .login p{ margin: 10px 50px; font-size: 20px; font-weight: bold; line-height: 125%; } .login input{ margin: 5px 50px; } button{ display: block; border-style: none; font-size: 1.4em; background-color: #2b52cd; color: white; margin: 50px 50px; padding: 10px 30px; } .error{ font-size: 0.5em; color: #ce0404; } |
現時点では処理を書いていないので、まだ何も起きません。
認証する関数
そうしたら、実際の「ルーティングと認証機能を Python で実装」していきます。
ログイン機構
まずは、ログイン機構を auth.py に書きましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | # auth.py内 from firebase_admin import auth from datetime import timedelta def _login(email, password, expires=5): with open('private/apikey.txt') as f: api_key = f.read() url = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key={}'.format(api_key) headers = {'Content-type': 'application/json'} data = json.dumps({'email': email, 'password': password, 'returnSecureToken': True}) result = requests.post(url=url, headers=headers, data=data, ) result = result.json() # エラーがある場合 if 'error' in result: return result, None # セッションクッキーの作成 session_cookie = auth.create_session_cookie(result['idToken'], timedelta(expires)) return result, session_cookie |
Firebase 側で、セッション Cookie を作成します。
WebAPI が異なるだけで、サインアップとあまり変わりません。
この取得した結果にエラーが含まれていると、認証失敗です。
ルーティング
次に、 controllers.py に「/login」と「/admin」をルーティングします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | # controllers.py内 from auth import _login, _sign_up # ~~ 省略 ~~ # firebase_error_massages = { 'EMAIL_NOT_FOUND': 'メールアドレスが正しくありません', 'INVALID_PASSWORD': 'パスワードが正しくありません', } @api.route('/login') def login(req, resp): # エラーがあれば取得 error = req.params.get('error', '') if error in firebase_error_massages: error = firebase_error_massages[error] resp.html = api.template('login.html', title='Login', error=error ) @api.route('/admin') class Admin: async def on_get(self, req, resp): api.redirect(resp, '/login') async def on_post(self, req, resp): # POSTデータを取得 data = await req.media() email = data['email'] password = data['password'] # 認証 res, session = _login(email, password) if 'error' not in res: # 認証成功ならば管理者ページへ resp.html = api.template('admin.html', title='管理者ページ', token=res['idToken'], name=res['displayName']) else: # 認証失敗ならばエラーメッセージをログイン画面に渡してリダイレクト api.redirect(resp, '/login?error={}'.format(res['error']['errors'][0]['message'])) |
「/admin」では、GET でのアクセスはすべて「/login」にアクセスするようにします。
この時、クラス自身にルーティングを施して、 on_get() と on_post() で処理を分けることができます。
動作確認
では、実際にログインしてみましょう。
成功すれば、以下の様な画面に遷移されるはずです。
ログインに失敗すると…
例として、失敗した時も見ておきましょう。
第3回へつづく!
今回は、「Firebase 周りのセットアップおよび動作確認」を行いました。
一応認証機能は作ることができましたが、まだまだ不便です。
例えば、一度ログインに成功していれば、直接管理者ページに行けるのが普通です。
しかし、今は全てリダイレクトされてしまいます。
次回は、「認証機能の整備とログアウト機構」もつけてみましょう!
次回の記事はこちら
第1回はこちら
こちらの記事もオススメ!
書いた人はこんな人
- 「好きを仕事にするエンジニア集団」の(株)ライトコードです!
ライトコードは、福岡、東京、大阪の3拠点で事業展開するIT企業です。
現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。
いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。
システム開発依頼・お見積もり大歓迎!
また、現在「WEBエンジニア」「モバイルエンジニア」「営業」「WEBデザイナー」「WEBディレクター」を積極採用中です!
インターンや新卒採用も行っております。
以下よりご応募をお待ちしております!
https://rightcode.co.jp/recruit
- ライトコードの日常2月 29, 2024座談会はじめました!ライトコードの話ちょっと聞いてみませんか?
- ライトコードの日常12月 27, 2023年忘れ!ライトコード大忘年会2023
- ライトコードの日常12月 1, 2023ライトコードクエスト〜東京オフィス歴史編〜
- ITエンタメ10月 13, 2023Netflixの成功はレコメンドエンジン?