【#2】FlaskアプリにAuth0つけようとしたらapp.pyがぶっ壊れた話

に公開

Flaskは1ファイルで済むって言ったやつ出てこい

0. はじめに

本番環境に公開するためにAuth0入れてみたけどapp.pyが肥大化して詰んだ。
当初はAuth0をapp.pyに直接投入していたが、ルーティングぐちゃぐちゃになってページが開けなくなったので泣く泣く分割。
ハマった知見とかも共有しときます。

1. フル装備app.pyの地獄

改修前のディレクトリ構成
.
├── static
│   └── style.css
├── templates
│   ├── index.html
│   ├── report.html
│   └── layout.html
├── .dockerignore
├── .env
├── .envsample
├── .gitignore
├── app.py
├── README.md
└── requirements.txt

このapp.pyに初期画面、PDFプレビュー画面、認証画面、APIの各ルーティングをすべて入れて@require_authでロックを掛けた結果、想定通りにページが開けなくなって撃沈。
整理しきれないので泣く泣くapp.pyを分割することにした。

app.py
@app.route("/generate_pdf")
@require_auth
def pdf(): ...
@app.route("/login")
@require_auth
def login(): ...

2. 構成と認証をちゃんと分離

改修後のディレクトリ構成
.
├── papyrus
│   ├── __init__.py
│   ├── api_routes.py
│   ├── auth.py
│   ├── auth_routes.py
│   ├── db.py
│   └── routes.py
├── static
│   └── style.css
├── templates
│   ├── index.html
│   ├── report.html
│   └── layout.html
├── .dockerignore
├── .env
├── .env.sample
├── .gitignore
├── README.md
├── requirements.txt
└── run.py

改修前:app = Flask(__name__)をグローバル変数定義して使用
改修後:create_app()を導入し、app.pyの役割を分割

from papyrus import create_app
from dotenv import load_dotenv

load_dotenv()
app = create_app()

if __name__ == "__main__":
    app.run(debug=True)
from flask import Flask
from papyrus.routes import register_routes
from papyrus.api_routes import register_api_routes
from papyrus.auth import init_auth
from papyrus.auth_routes import register_auth_routes
from .db import init_db
from dotenv import load_dotenv
import os

load_dotenv()
def create_app():
    BASE_DIR = os.path.abspath(os.path.dirname(__file__))
    TEMPLATE_DIR = os.path.join(BASE_DIR, '../templates')
    app = Flask(__name__, template_folder=TEMPLATE_DIR)
    app.secret_key = os.getenv("FLASK_SECRET_KEY")

    init_auth(app)
    init_db(app)
    register_routes(app)
    register_api_routes(app)
    register_auth_routes(app)
    return app

ポイント

  • load_dotenv()app = Flask(__name__)よりも前で実行

    • .envファイルの情報が抜けるとcreate_app()でエラー
  • app = Flask(__name__)だとtemplates\のファイルを読み込んでくれないので、中にtemlate_folerを設定して明示的にディレクトリ指定する。

  • create_app()内で読み込むルーティング関数は、必要なものすべてそろっているか確認する。

3. Auth0導入

Auth0にログインする。アカウントがなければ作る。
「使用を開始」>「アプリケーションの開始」をクリック

「一般的なWebアプリケーション」を選択する。

Pythonを選択

「クイックスタート」を選び下へスクロール

「Configure your.env file」をコピーしてAPP_SECRET_KEY以外を.envに貼りつけ

「設定」>「アプリケーションのURI」欄に入力

Configuration URI
# 許可するコールバックURL
http://localhost:5000/callback

# 許可するログアウトURL
http://localhost:5000/logout

そして...Auth0と連携成功


Googleアカウントと連携していると警告が出る(本番up禁止!)

ターミナルでpython3 -c 'import secrets; print(secrets.token_urlsafe(32))'を実行し、その結果を.envFLASK_SECRET_KEY=に貼りつけ

4. 他の問題点の解消

元のapp.pyでは、納品リストをグローバル変数 delivery_list = [] として定義していた。
しかしこの実装では、複数ユーザーが同時にアクセスした場合に納品リストの内容が全ユーザー間で共有されてしまうため、セッションごとの分離ができていなかった。

この問題を解消するため、Flaskのセッション(session)を使用して、ユーザーごとにdelivery_listを個別管理するよう変更した。

変更後の実装:

# GET: 納品リストの表示
@app.route("/index")
@requires_auth
def index():
    form_data = {}
    delivery_list = session.get("delivery_list", [])
    return render_template("index.html", delivery_list=delivery_list, form_data=form_data)
# POST: 納品リストの更新
@app.route("/index", methods=["POST"])
@requires_auth
def handle_submit():
    form_data = {}
    delivery_list = session.get("delivery_list", [])
    action = request.form.get("action")

    # ここで delivery_list を編集してから session に戻す処理を書く(省略)

    session["delivery_list"] = delivery_list
    return render_template("index.html", delivery_list=delivery_list, form_data=form_data)

5. おわりに

最終的にAuth0取付できて、次回予定の本番環境構築準備ができた。
app.pyオンリーの構成がapp.py分割, create_app()導入, Auth0導入, セッション管理で本格構成にすることができた。
次はVPSに上げて本番環境の構築を試したい。

Discussion