• トップ
  • ブログ一覧
  • 【第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...

    ライトコードでは、エンジニアを積極採用中!

    ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。

    採用情報へ

    メディアチーム
    メディアチーム
    Show more...

    おすすめ記事

    エンジニア大募集中!

    ライトコードでは、エンジニアを積極採用中です。

    特に、WEBエンジニアとモバイルエンジニアは是非ご応募お待ちしております!

    また、フリーランスエンジニア様も大募集中です。

    background