Flask-Login — ユーザー登録、ログイン、ログアウト
この章では、Flask-Login を使用して Session ユーザー認証を実装する方法を学びます。これは Flask Web 開発において最も重要なセキュリティ対策です。
Flask-Login とは?
Web アプリケーションの HTTP リクエストは本質的にステートレスです。サーバーはあなたが誰であるかを自動的に記憶しません。
Flask-LoginによってSessionメカニズムがユーザー状態の維持を実現します:ログイン後、サーバーは Session にユーザー ID を保存し、後続のリクエストは現在のユーザーを自動的に識別します。
インストールと初期化
(venv) $ pip install flask-login
例
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db'
app.config['SECRET_KEY'] = 'your-secret-key-here' # Session 暗号化キー(本番環境では環境変数を使用)
db = SQLAlchemy(app)
# LoginManager を初期化
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'auth.login' # 未ログインユーザーはログインページにリダイレクト
login_manager.login_message = '先にログインしてからアクセスしてください。'
SECRET_KEYFlask セキュリティ体系の基盤です。Session 暗号化、CSRF Token、Flash メッセージはすべてこれに依存します。本番環境では環境変数から読み取る必要があり、ハードコーディングしてはなりません。
User モデルを定義する
Flask-Login は User モデルに実装を要求しますUserMixin提供されるいくつかのメソッド。
例
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
class User(UserMixin, db.Model):
"""ユーザーモデル"""
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(50), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(256), 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)
def __repr__(self):
return f'<User {self.username}>'
# user_loader を定義:Flask-Login はこの関数を使用して session からユーザーオブジェクトを復元します
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
UserMixin は User モデルに以下の属性とメソッドを自動的に注入します:
| 属性/メソッド | 説明 |
|---|---|
| is_authenticated | ユーザーがログインしているかどうか |
| is_active | ユーザーがアクティブ状態かどうか |
| is_anonymous | 匿名ユーザー(未ログイン)かどうか |
| get_id() | ユーザーの主キー ID を取得する |
マイグレーションを生成して実行する:
(venv) $ flask db migrate -m "新增 User 模型" (venv) $ flask db upgrade
認証 Blueprint を作成する
例
from flask import Blueprint, render_template, redirect, url_for, request, flash
from flask_login import login_user, logout_user, login_required, current_user
from app.models import User, db
auth_bp = Blueprint('auth', __name__)
@auth_bp.route("/register", methods=['GET', 'POST'])
def register():
"""ユーザー登録"""
if current_user.is_authenticated:
return redirect(url_for('main.index'))
if request.method == 'POST':
username = request.form.get('username', '').strip()
email = request.form.get('email', '').strip()
password = request.form.get('password', '')
# 必須フィールドを検証
if not username or not email or not password:
flash('すべてのフィールドに入力する必要があります。', 'error')
return render_template('register.html')
# ユーザー名とメールアドレスが既に存在するか確認
if User.query.filter_by(username=username).first():
flash('このユーザー名は既に使用されています。', 'error')
return render_template('register.html')
if User.query.filter_by(email=email).first():
flash('このメールアドレスは既に登録されています。', 'error')
return render_template('register.html')
# ユーザーを作成
user = User(username=username, email=email)
user.set_password(password) # パスワードを暗号化して保存
db.session.add(user)
db.session.commit()
login_user(user) # 登録後に自動ログイン
flash(f'登録成功、ようこそ、{username}さん!', 'success')
return redirect(url_for('main.index'))
return render_template('register.html')
@auth_bp.route("/login", methods=['GET', 'POST'])
def login():
"""ユーザーログイン"""
if current_user.is_authenticated:
return redirect(url_for('main.index'))
if request.method == 'POST':
username = request.form.get('username', '').strip()
password = request.form.get('password', '')
# user_loader がデータベースからユーザーを検索
user = User.query.filter_by(username=username).first()
# パスワードを検証(パスワード自体はログに記録されません)
if user is None or not user.check_password(password):
flash('ユーザー名またはパスワードが正しくありません。', 'error')
return render_template('login.html')
login_user(user, remember=request.form.get('remember'))
flash(f'おかえりなさい、{username}さん!', 'success')
# ログイン後、ログイン前にアクセスしていたページにリダイレクト(URL の next パラメータ)
next_page = request.args.get('next')
return redirect(next_page or url_for('main.index'))
return render_template('login.html')
@auth_bp.route("/logout")
@login_required # ログインユーザーのみログアウトを実行できます
def logout():
logout_user()
flash('安全にログアウトしました。', 'info')
return redirect(url_for('main.index'))
App 設計で必要な参照
例
from app.blueprints.auth import auth_bp
app.register_blueprint(auth_bp)
パスワードを平文で保存してはいけません!
generate_password_hash()PBKDF2 アルゴリズムを使用してパスワードを salted hash にします。データベースが漏洩しても、攻撃者はパスワードを逆算できません。パスワードを検証するときはcheck_password_hash()。
ログインと登録のテンプレートを作成する
例
{% extends 'base.html' %}
{% block title %}ログイン - サンプル ブログ{% endblock %}
{% block content %}
<div class="auth-form">
<h2>ログイン</h2>
<form method="post">
<div class="form-group">
<label>ユーザー名</label>
<input type="text" name="username" required>
</div>
<div class="form-group">
<label>パスワード</label>
<input type="password" name="password" required>
</div>
<div class="form-group">
<label>
<input type="checkbox" name="remember">ログイン状態を保持する
</label>
</div>
<button type="submit" class="btn-submit">ログイン</button>
</form>
<p class="form-footer">
アカウントをお持ちでないですか?<a href="{{ url_for('auth.register') }}">今すぐ登録</a>
</p>
</div>
{% endblock %}
ナビゲーションバーを更新してログイン状態を表示する
例
<nav>
<a href="/">ホーム</a>
{% if current_user.is_authenticated %}
<span class="user-name">{{ current_user.username }}</span>
<a href="{{ url_for('auth.logout') }}">ログアウト</a>
{% else %}
<a href="{{ url_for('auth.login') }}">ログイン</a>
<a href="{{ url_for('auth.register') }}">登録</a>
{% endif %}
</nav>
current_userFlask-Login が自動的に注入するプロキシオブジェクトで、すべてのテンプレートとビュー関数で直接使用できます。ログイン状態では User オブジェクト、未ログイン時は AnonymousUserMixin インスタンスです。
本章のまとめ
この章では Flask-Login の Session 認証を導入しました。UserMixin + user_loader でユーザーモデルを構成、login_user/logout_user で Session を管理、werkzeug でパスワードをハッシュ化、@login_required でビューを保護、テンプレート内で current_user を使って状態を判定します。
これでブログに完全なユーザー登録・ログイン・ログアウトのフローが備わりました。
その他の拡張