 メディア編集部
メディア編集部【第2回】Responder + Firestore でモダンかつサーバーレスなブログシステムを作ってみた【Firebase設定と認証機能実装】
 メディア編集部
メディア編集部IT技術

第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 とします。
1import firebase_admin
2from firebase_admin import credentials
3from firebase_admin import firestore
4
5# Use a service account
6cred = credentials.Certificate('private/keys.json')
7firebase_admin.initialize_app(cred)
8
9db = firestore.client()このファイルを実行しても何も表示されませんが、これでサーバ準備は完了です。
注意!
ここで使った json ファイルは、絶対に外部に漏らしてはいけません。
Github にあげたりする場合は、これらのファイルは外してください。
本番サーバに上げる場合も、private ディレクトリは .htaccess でアクセスできないようにしておきましょう。
動作確認
データを追加
それでは、実際に、データを追加してみましょう。
Firestore では、Python ファイルからもデータを追加できます。
以下のコードを参照してください。
1from init_db import db
2
3doc_ref = db.collection('users').document('TTanaka')
4doc_ref.set({
5    'first': 'Taro',
6    'last': 'Tanaka',
7    'born': 1994
8})これを実行してみると、以下のようにデータが追加できていることがわかります。

ここでは、コレクションは「モデル」、ドキュメントは「ID(主キー)」にあたります。
データの取り出し
データの取り出しは、以下のようにできます。
1from init_db import db
2
3users_ref = db.collection('users')
4docs = users_ref.stream()
5
6for doc in docs:
7    print('{} => {}'.format(doc.id, doc.to_dict()))1TTanaka => {'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 を作り、サインアップ関数を作ります。
1import requests
2import json
3
4
5def _sign_up(email, password, displayName):
6    # Api key取得
7    with open('private/apikey.txt') as f:
8        api_key = f.read()
9
10    # 管理者ユーザ追加
11    url = 'https://identitytoolkit.googleapis.com/v1/accounts:signUp?key={}'.format(api_key)
12    headers = {'Content-type': 'application/json'}
13    data = json.dumps({'email': email,
14                       'password': password,
15                       'returnSecureToken': True})
16
17    result = requests.post(url=url,
18                           headers=headers,
19                           data=data,
20                           )
21
22    if 'error' in result.json():
23        print('error: {}'.format(result.json()['error']['errors'][0]['message']))
24        return result.json()
25
26    # DisplayNameを更新
27    url = 'https://identitytoolkit.googleapis.com/v1/accounts:update?key={}'.format(api_key)
28    data = json.dumps({
29        'idToken': result.json()['idToken'],
30        'displayName': displayName
31    })
32
33    result = requests.post(url=url,
34                           headers=headers,
35                           data=data,
36                           )
37
38    if 'error' in result.json():
39        print('error: {}'.format(result.json()['error']['errors'][0]['message']))
40        return result.json()
41
42    return result.json()
43
44
45if __name__ == '__main__':
46    _sign_up('contact@sample.com', 'password', 'rightcode')複雑に見えますが、やっていることは「WebAPI に json を投げている」だけです。
apikey.txt を /private ディレクトリに用意
ここで、API Keyを記述したapikey.txt を「/private」ディレクトリに用意しておいてください。
「プロジェクトの設定」で確認できます。
実行
それでは、実行してみましょう。
そうすると、以下の様にユーザが追加できていることがわかります。

管理者ページとログイン画面を作る
認証機能を実装する前に、先に「管理者ページ」と「ログイン画面」を仮で作っておきます。
管理者ページ
「templates/admin.html」を作り、以下のように記述します。
1<!--
2templates/admin.html
3管理者ページ
4-->
5
6{% extends "layout.html" %}
7{% block content %}
8
9<br>
10<h1>My Blog Name | 管理者ページ</h1>
11<p>こんにちは,{{ name }} さん</p>
12
13{% endblock %}ひとまず、管理者ページはこれだけです。
ログインページ
次にログインページですが、今回は以下の様にしました。
1<!--
2templates/login.html
3ログインページ
4-->
5
6{% extends "layout.html" %}
7{% block content %}
8
9<br>
10<h1>My Blog Name | ログイン</h1>
11<br>
12<div class="card login">
13    <form action="/admin" method="post">
14        <p>Email</p>
15        <input name="email" type="email">
16        <p>Password</p>
17        <input name="password" type="password">
18        <p class="error">{{ error }}</p>
19        <button type="submit">Login</button>
20    </form>
21</div>
22
23{% endblock %}CSSも追記します。
1.card{
2    max-width: 500px;
3    height: auto;
4    margin: auto;
5}
6.login{
7    overflow: hidden;
8    border-radius: 8px;
9    box-shadow: 0 4px 15px rgba(0,0,0,.2);
10}
11.login form{
12    margin: 50px 10px;
13}
14.login p{
15    margin: 10px 50px;
16    font-size: 20px;
17    font-weight: bold;
18    line-height: 125%;
19}
20.login input{
21    margin: 5px 50px;
22}
23button{
24    display: block;
25    border-style: none;
26    font-size: 1.4em;
27    background-color: #2b52cd;
28    color: white;
29    margin: 50px 50px;
30    padding: 10px 30px;
31}
32.error{
33    font-size: 0.5em;
34    color: #ce0404;
35}
現時点では処理を書いていないので、まだ何も起きません。
認証する関数
そうしたら、実際の「ルーティングと認証機能を Python で実装」していきます。
ログイン機構
まずは、ログイン機構をauth.py に書きましょう。
1# auth.py内
2from firebase_admin import auth
3from datetime import timedelta
4
5
6def _login(email, password, expires=5):
7    with open('private/apikey.txt') as f:
8        api_key = f.read()
9
10    url = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key={}'.format(api_key)
11    headers = {'Content-type': 'application/json'}
12    data = json.dumps({'email': email,
13                       'password': password,
14                       'returnSecureToken': True})
15
16    result = requests.post(url=url,
17                           headers=headers,
18                           data=data,
19                           )
20
21    result = result.json()
22
23    # エラーがある場合
24    if 'error' in result:
25        return result, None
26
27
28    # セッションクッキーの作成
29    session_cookie = auth.create_session_cookie(result['idToken'], timedelta(expires))
30
31    return result, session_cookieFirebase 側で、セッション Cookie を作成します。
WebAPI が異なるだけで、サインアップとあまり変わりません。
この取得した結果にエラーが含まれていると、認証失敗です。
ルーティング
次に、controllers.py に「/login」と「/admin」をルーティングします。
1# controllers.py内
2from auth import _login, _sign_up
3
4# ~~ 省略 ~~ #
5
6firebase_error_massages = {
7    'EMAIL_NOT_FOUND': 'メールアドレスが正しくありません',
8    'INVALID_PASSWORD': 'パスワードが正しくありません',
9}
10
11@api.route('/login')
12def login(req, resp):
13    # エラーがあれば取得
14    error = req.params.get('error', '')
15    if error in firebase_error_massages:
16        error = firebase_error_massages[error]
17
18    resp.html = api.template('login.html',
19                             title='Login',
20                             error=error
21                             )
22
23
24@api.route('/admin')
25class Admin:
26    async def on_get(self, req, resp):
27        api.redirect(resp, '/login')
28
29    async def on_post(self, req, resp):
30        # POSTデータを取得
31        data = await req.media()
32        email = data['email']
33        password = data['password']
34
35        # 認証
36        res, session = _login(email, password)
37
38        if 'error' not in res:
39            # 認証成功ならば管理者ページへ
40            resp.html = api.template('admin.html',
41                                     title='管理者ページ',
42                                     token=res['idToken'],
43                                     name=res['displayName'])
44        else:
45            # 認証失敗ならばエラーメッセージをログイン画面に渡してリダイレクト
46            api.redirect(resp, '/login?error={}'.format(res['error']['errors'][0]['message']))「/admin」では、GET でのアクセスはすべて「/login」にアクセスするようにします。
この時、クラス自身にルーティングを施して、on_get() とon_post() で処理を分けることができます。
動作確認
では、実際にログインしてみましょう。
成功すれば、以下の様な画面に遷移されるはずです。

ログインに失敗すると…
例として、失敗した時も見ておきましょう。

第3回へつづく!
今回は、「Firebase 周りのセットアップおよび動作確認」を行いました。
一応認証機能は作ることができましたが、まだまだ不便です。
例えば、一度ログインに成功していれば、直接管理者ページに行けるのが普通です。
しかし、今は全てリダイレクトされてしまいます。
次回は、「認証機能の整備とログアウト機構」もつけてみましょう!
次回の記事はこちら
第1回はこちら
こちらの記事もオススメ!
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ

「好きを仕事にするエンジニア集団」の(株)ライトコードです! ライトコードは、福岡、東京、大阪、名古屋の4拠点で事業展開するIT企業です。 現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。 いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。 システム開発依頼・お見積もり大歓迎! また、現在「WEBエンジニア」「モバイルエンジニア」「営業」「WEBデザイナー」を積極採用中です! インターンや新卒採用も行っております。 以下よりご応募をお待ちしております! https://rightcode.co.jp/recruit














