【Flask】Flask-Loginでログイン機能を実装する

Flask

こんにちは、かつコーチです。

これまでの記事でデータベースの基本操作を学びました。

今回は、その知識を活かして、Webアプリに欠かせない「ログイン機能」をFlask-Loginという拡張ライブラリで実装します。

Flask-Loginとは?

セッション管理を担うライブラリ

Flask-Loginとは、ログイン状態の管理(セッション管理:ユーザーがログインしているかどうかをブラウザとサーバー間で覚えておく仕組み)を簡単に実装できるようにするFlaskの拡張ライブラリです。

パスワードのハッシュ化やユーザー登録フォームそのものは提供しない、あくまで「ログイン状態の維持」に特化したライブラリという点を覚えておいてください。

pip install flask-login

なぜパスワードをそのまま保存してはいけないのか

ログイン機能を作る上で絶対に押さえておきたいのが、パスワードの扱いです。

パスワードをデータベースにそのまま(平文)で保存すると、万が一データが漏えいした際に、ユーザーのパスワードがそのまま流出してしまいます。

そのため、パスワードはハッシュ化(元のデータに戻せない形式に不可逆変換する処理)してから保存するのが鉄則です。

Flaskでは、Werkzeug(Flaskの内部で使われているライブラリ)に含まれるgenerate_password_hashcheck_password_hashを使うのが一般的です。

基本の書き方・実装手順

手順1: ユーザーモデルにUserMixinを継承させる

Flask-Loginを使うには、ユーザーモデルにUserMixinというクラスを継承させる必要があります。

from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash

db = SQLAlchemy()

class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password_hash = db.Column(db.String(255), nullable=False)

    def set_password(self, password):
        self.password_hash = generate_password_hash(password)

    def check_password(self, password):
        return check_password_hash(self.password_hash, password)

UserMixinが、Flask-Loginが内部で必要とするis_authenticatedget_idといったメソッドを自動的に用意してくれます。

手順2: LoginManagerの初期設定

from flask import Flask
from flask_login import LoginManager

app = Flask(__name__)
app.config["SECRET_KEY"] = "your-secret-key-here"

login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = "login"

@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

SECRET_KEYは、セッション情報を暗号化するために使われる重要な値です。

本番環境では、コードに直接書かず環境変数から読み込むようにしてください。

user_loaderは、セッションに保存されたユーザーIDから、実際のユーザー情報を取得するための関数です。

手順3: ログイン・ログアウト処理を実装する

from flask import request, redirect, url_for, render_template
from flask_login import login_user, logout_user, login_required, current_user

@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":
        email = request.form.get("email")
        password = request.form.get("password")
        user = User.query.filter_by(email=email).first()

        if user and user.check_password(password):
            login_user(user)
            return redirect(url_for("dashboard"))
        return "メールアドレスまたはパスワードが違います", 401

    return render_template("login.html")

@app.route("/logout")
@login_required
def logout():
    logout_user()
    return redirect(url_for("login"))

@app.route("/dashboard")
@login_required
def dashboard():
    return f"ようこそ、{current_user.email}さん"

login_user(user)でログイン状態をセッションに保存し、logout_user()で解除します。

@login_requiredデコレータを付けたルートは、ログインしていないユーザーがアクセスすると自動的にlogin_viewで指定したログインページへリダイレクトされます。

つまずきやすい設定・注意点

SECRET_KEYの管理

SECRET_KEYは、セッションの改ざんを防ぐための重要な値です。

この値が推測されやすかったり、コードと一緒にGitHubなどへ公開されてしまったりすると、セッション情報が偽造されるリスクが生じます。

import os
app.config["SECRET_KEY"] = os.environ.get("SECRET_KEY", "dev-only-key")

このように環境変数から読み込む形にしておき、本番環境では十分に複雑なランダム文字列を設定する運用が安全です。

よくあるつまずきポイント・エラー対処

RuntimeError: The session is unavailable

❌ Before:SECRET_KEYを設定せずにログイン機能を実装し、login_user(user)を呼び出した瞬間にRuntimeError: The session is unavailable because no secret key was setというエラーが出る

✅ After:app.config["SECRET_KEY"]に何らかの値を必ず設定してからアプリを起動する

私が初めてFlask-Loginを試したときに最初に遭遇したのがこのエラーでした。

Flask-Loginはセッション機能を利用してログイン状態を管理する仕組みなので、Flask本体のセッション機能を有効にするSECRET_KEYの設定が前提条件になります。

ログインしているのにcurrent_userが空になる

❌ Before:user_loaderの中でUser.query.get(user_id)を実装したつもりが、int(user_id)への変換を忘れていて、文字列のIDとデータベースの整数IDが一致せずcurrent_userが正しく取得できない

@login_manager.user_loader
def load_user(user_id):
    return User.query.get(user_id)  # user_idは文字列のまま

✅ After:セッションに保存されるユーザーIDは常に文字列型で渡されるため、int(user_id)で明示的に変換してから検索する

@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

このバグは、ログイン処理自体はエラーなく成功しているように見えるため、原因を探すのに少し時間がかかりました。

Flask-Loginのuser_loaderに渡されるIDは常に文字列であるという仕様を知っていれば、すぐに気づけるポイントです。

応用・一歩先の使い方

remember=Trueでログイン状態を保持する

login_user(user, remember=True)のようにremember引数を指定すると、ブラウザを閉じてもクッキーによってログイン状態を一定期間保持できます。

「ログイン状態を保持する」というチェックボックスを実装したい場合に活用できます。

ロール(権限)による制御を追加する

管理者と一般ユーザーで見せる画面を分けたい場合は、ユーザーモデルにroleカラムを追加し、独自のデコレータで制御する方法が一般的です。

from functools import wraps
from flask import abort

def admin_required(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        if not current_user.is_authenticated or current_user.role != "admin":
            abort(403)
        return f(*args, **kwargs)
    return decorated

@login_requiredと組み合わせることで、認証と認可(権限チェック)を分けて実装できます。

まとめ

この記事のポイント

  • Flask-Loginはログイン状態のセッション管理に特化したライブラリ
  • パスワードはgenerate_password_hashでハッシュ化して保存する
  • UserMixinの継承、LoginManagerの初期設定、user_loaderの実装が基本の3点セット
  • SECRET_KEY未設定はFlask-Login利用時の頻出エラー原因
  • user_loaderではユーザーIDをint()で変換することを忘れない

次に読むべき記事

認証機能を実装したら、そのコードが意図通りに動いているかを保証するテストの書き方を学びましょう。

「pytestでFlaskアプリの単体テストを書く」に進んでください。


タグ: Flask, 中級者向け, 認証

タイトルとURLをコピーしました