• トップ
  • ブログ一覧
  • 【第2回】Responder + Firestore でモダンかつサーバーレスなブログシステムを作ってみた【Firebase設定と認証機能実装】
  • 【第2回】Responder + Firestore でモダンかつサーバーレスなブログシステムを作ってみた【Firebase設定と認証機能実装】

    広告メディア事業部広告メディア事業部
    2020.07.20

    IT技術

    第2回~モダンなフレームワークの使い方を学びながらブログシステムを構築~

    本記事は、「Python Responder + Firestore で、モダンかつサーバーレスなブログシステムを作ってみる」第2回目です。

    前回は、初期設定として「Responder のセットアップ」まで解説しました。

    featureImg2020.07.17【第1回】Responder + Firestore でモダンかつサーバーレスなブログシステムを作ってみた【初期セットアップ編】モダンなフレームワークの使い方を学びながらブログシステムを構築今回より、また新たに Python WebAPI 関連の...

    今回は、「Firestore のセットアップ」を中心に解説していきます。

    Firestore セットアップ

    Firestore セットアップの手順

    Firestore セットアップの大まかな流れは、以下の通りです。

    1. プロジェクトを作成する
    2. データベースを作成する
    3. サーバを初期化する
    4. 動作確認

    公式ドキュメントも参照してみてください。

    【Cloud Firestore を使ってみる】
    https://firebase.google.com/docs/firestore/quickstart?hl=ja#python

    プロジェクト作成

    まずは、Firebase でプロジェクトを生成します。

    「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_cookie

    Firebase 側で、セッション 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 周りのセットアップおよび動作確認」を行いました。

    一応認証機能は作ることができましたが、まだまだ不便です。

    例えば、一度ログインに成功していれば、直接管理者ページに行けるのが普通です。

    しかし、今は全てリダイレクトされてしまいます。

    次回は、「認証機能の整備とログアウト機構」もつけてみましょう!

    次回の記事はこちら

    featureImg2020.07.22【第3回】Responder + Firestore でモダンかつサーバーレスなブログシステムを作ってみた【管理者ページ整備】~第3回~モダンなフレームワークの使い方を学びながらブログシステムを構築連載「Python Responder + F...

    第1回はこちら

    featureImg2020.07.17【第1回】Responder + Firestore でモダンかつサーバーレスなブログシステムを作ってみた【初期セットアップ編】モダンなフレームワークの使い方を学びながらブログシステムを構築今回より、また新たに Python WebAPI 関連の...

    こちらの記事もオススメ!

    featureImg2020.07.17ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...

    featureImg2020.07.30Python 特集実装編※最新記事順Responder + Firestore でモダンかつサーバーレスなブログシステムを作ってみた!P...

    広告メディア事業部

    広告メディア事業部

    おすすめ記事