FlaskアプリにFirebase Authを利用したログイン機能を実装する

#はじめに
Firebase Authを利用してFlaskアプリにログイン機能を導入します。
メールアドレスとパスワードによる認証をサンプルアプリに実装していきます。

# Firebaseの設定
はじめに、Firebaseのコンソール上でログインユーザーを登録します。
## プロジェクト/アプリの作成
Firebaseのコンソールにログインし、新しいプロジェクトを作ります。
<img width="928" alt="スクリーンショット 2019-12-26 14.17.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101644/a3208313-0482-ea27-9fe8-f9dc436d7671.png">


作成したプロジェクトに新しいWEBアプリケーションを追加します。
<img width="814" alt="スクリーンショット 2019-12-21 17.44.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101644/70cbc499-fe3f-9da5-8ccc-9dfb4126d987.png">
アプリを追加すると以下のように設定情報が表示されるので、jsonファイル`firebaseConfig.json`に転記します(今回はサンプルアプリであり、記事投稿後にプロジェクトごと削除するので、クリティカルな情報をそのまま載せています^^;)。

<img width="1204" alt="スクリーンショット 2019-12-24 19.27.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101644/094214b1-a1ec-162e-c1eb-6893b13d4b7f.png">

```json:firebaseConfig.json
{
    "apiKey": "AIzaSyAMotj5BYoi8HQbYdw_HqsOTy1NitUTzm8",
    "authDomain": "example-fd5bd.firebaseapp.com",
    "databaseURL": "https://example-fd5bd.firebaseio.com",
    "projectId": "example-fd5bd",
    "storageBucket": "example-fd5bd.appspot.com",
    "messagingSenderId": "203038233678",
    "appId": "1:203038233678:web:86a73fe39a252f8300df1f",
    "measurementId": "G-Y6Q7XMDM2V"
}
```

## ユーザーの登録
ログイン用のユーザーを追加します。まず、コンソールの`Authentification > ログイン方法`からメール/パスワードを有効にします。
<img width="1159" alt="スクリーンショット 2019-12-24 19.34.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101644/6d553a3a-d68e-a344-4106-8f3a3d79d087.png">

次に、`Authentification > ユーザー`から認証を行うアカウントを追加します。メールアドレスは`user@gmail.com`、パスワードは`password`としています。
<img width="1165" alt="スクリーンショット 2019-12-24 19.35.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101644/0989773c-4d3e-9446-0abe-857f867a6edc.png">
これでFirebaseでの作業は完了です。

# Flaskアプリの実装
Firebase側の設定は完了したので、Flaskアプリのコーディングを行います。

## インストール
パッケージとして`flask`と`pyrebase`をインストールします。`pyrebase`はFirebase APIのpythonラッパーです。

```
pip install flask pyrebase
```

## コード
短いので完成品のコードをそのまま載せます。


### ディレクトリ構造
ディレクトリ構造は以下の通りです。`app.py`はFlaskアプリで、ログイン画面とインデックス画面があります。認証が行われていない状態ではインデックス画面にアクセスできず、ログイン画面にリダイレクトされます。`firebaseConfig.json`は前の手順で作ったもので、`app.py`から読み込まれます。

```bash
$ tree . -I venv
.
├── app.py
├── firebaseConfig.json
└── templates
    ├── index.html
    └── login.html

1 directory, 4 files
```

### ログイン画面
ログイン画面`login.html`は以下の通りです。
フォームからメールアドレスとパスワードを入力しPOSTします。
ログインに失敗しリダイレクトされた場合は、オレンジ色で「メールアドレスまたはパスワードが間違っています」とメッセージが表示されます。

```html:templates/login.html
<html>
<head>
  <meta charset="utf-8">
  <title>Flask app with Firebase authentification</title>
</head>
<body>

<div>
    <h1>メールアドレスとパスワードを入力してください</h1>
    <form action='login' method='POST'>
        <input type='text' name='email' id='email' placeholder='email'/>
        <br/>
        <input type='password' name='password' id='password' placeholder='password'/>
        <br/>
        <input type='submit' name='submit'/>
    </form>
    <h2 style="color:orangered;">{{msg}}</h2>
</div>

</body>
</html>
```

ブラウザでは以下のように表示されます。
<img width="744" alt="スクリーンショット 2019-12-26 14.14.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101644/7729729e-c9b9-33b7-ef24-0ee4bc1b5471.png">


### インデックス画面
`index.html`はログイン状態でのみアクセスできます。ユーザーのメールアドレスを表示しています。ログアウトを押すと、ログイン画面にリダイレクトされます。

```html:templates/index.html
<html>
<head>
  <meta charset="utf-8">
  <title>Flask app with Firebase authentification</title>
</head>
<body>

<div>
  <h1>{{usr}}さん、こんにちは！</h1>
  <a href="/logout">ログアウト</a>
</div>

</body>
</html>
```
ブラウザでは以下のように表示されます。
<img width="742" alt="スクリーンショット 2019-12-26 14.13.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101644/2633f231-b9c2-8a76-084c-697c893a7bb7.png">


### Flaskアプリ
アプリ本体のコードの説明です。はじめに`firebaseConfig.json`を読み込み、初期化を行います。アクセス可能なルートとして`login`、`index`、`logout`の３つがあります。それぞれの機能は以下の通りです。

`login()`では、メソッドが`GET`だった場合には`login.html`を表示し、メソッドがPOSTだった場合にはリクエストフォームのパラメタ`email`と`password`を取り出し`sign_in_with_email_and_password`によりFirebase認証を行います。失敗した場合はエラーとなるので、例外処理でログイン画面にリダイレクトします。成功した場合は、セッション変数にユーザーのメールアドレスを格納し、インデックス画面にリダイレクトします。

`index()`では、セッション変数が存在するかどうかでログインしているかどうかを判定しています。存在していない場合はログイン画面にリダイレクトし、存在する場合は`index.html`を表示させます。

`logout()`では、セッション変数を削除してログイン画面にリダイレクトします。

```python:app.py
from flask import Flask, request, jsonify, render_template, redirect, url_for, session
import pyrebase
import json, os

with open("firebaseConfig.json") as f:
    firebaseConfig = json.loads(f.read())
firebase = pyrebase.initialize_app(firebaseConfig)
auth = firebase.auth()

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        return render_template("login.html",msg="")

    email = request.form['email']
    password = request.form['password']
    try:
        user = auth.sign_in_with_email_and_password(email, password)
        session['usr'] = email
        return redirect(url_for('index'))
    except:
        return render_template("login.html", msg="メールアドレスまたはパスワードが間違っています。")

@app.route("/", methods=['GET'])
def index():
    usr = session.get('usr')
    if usr == None:
        return redirect(url_for('login'))
    return render_template("index.html", usr=usr)

@app.route('/logout')
def logout():
    del session['usr']
    return redirect(url_for('login'))

# run the app.
if __name__ == "__main__":
    app.debug = True
    app.run(port=5000)
```

# まとめ
Firebase認証を使ってメールアドレスとパスワードによるログイン機能をFlaskアプリに導入することが出来ました。ログイン機能が必要になったときに手軽に導入できるのでおすすめです。アカウントの追加・停止、パスワード再発行などの操作をプラットフォーム上で行えるので、管理のコストも抑えられます。


ただし、Firebaseはモバイルアプリのプラットフォームなので、今回紹介したようにFlaskのセッション管理機能と組み合わせて使うことは珍しいかもしれません。