はじめに
webアプリを作っていると、認証システムが必要になるケースがあります。
- 登録されているユーザだけがアクセスできるようにしたい
- ユーザごとにデータを切り分けて管理したい
こういったケースで、flask_loginを導入することで既存のFlaskアプリケーションに簡単にログイン機能を追加できます。
考え方
flask_loginを使うことで、ユーザIDとパスワードによる認証機能を既存のFlaskアプリに追加できます。
具体的には、LoginManagerが保護したいエンドポイントに対して、認証されたユーザにしかアクセスできないよう制限をかけ、ログイン画面へのリダイレクトを行います。
ユーザIDとパスワードのデータはデータベースに保存します。
データベースの操作について、SQLite3を使用する方法もありますが、今回は外部DBへの移行も想定してsqlalchemyを使用します。
以下Geminiを使って作成したコードのFlask-login導入に関係する部分を抜き出し、説明します。
導入方法
flask, flask_login, sqlite3をpipでインストールします。
pip install flask
pip install flask-login
pip install flask_sqlalchemy
pip instal sql_alchemy
必要なパッケージのimportをします。
import uuid
from flask import Flask, redirect, url_for, request
from flask_login import (
LoginManager,
UserMixin,
login_user,
logout_user,
login_required,
current_user,
)
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import DeclarativeBase # SQLAlchemy 2.0+ 互換性のため
アプリケーションを新規に作成している場合はアプリケーションの初期化部分を記述する必要があります。既存のflaakアプリに追加する場合は不要です。
# --- アプリケーションと構成の初期化 ---
app = Flask(__name__)
# 開発・デモ用の一時的なシークレットキー。
app.secret_key = str(uuid.uuid4())
app.config['DEBUG'] = True
データベース操作についてはSQLAlchemyを使用します。
当然DBの配置を指定する必要があります。DB自体にはSQLiteを使用しています。
LoginManagerクラスのインスタンスを取得し、初期化します。
# --- SQLAlchemy 構成と初期化 ---
# データベース接続URIを設定 (SQLiteを使用)
# 外部データベース (PostgreSQLなど) に移行する際は、この行だけを変更すればOKです
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app_sqlalchemy.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # 推奨設定
# SQLAlchemyのベースクラスを定義 (2.0以降の標準的な方法)
class Base(DeclarativeBase):
pass
db = SQLAlchemy(app, model_class=Base)
# Flask-Loginの初期化
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login_page'
login_manager.login_message = "このページにアクセスするにはログインが必要です。"
UserMixinを継承したUserクラスを定義します。これを使ってUserオブジェクトを作り、UserIDからアクセスできるようにMAPに保管しておくことで、Flask_loginはセッションに保存された user_id からユーザー情報を復元できるようになります。
現在は"testuser"と"admin"の2つのユーザーしか管理していません。
新規ユーザーの登録については、現状では実装できていません。
load_user()メソッドのデコレーター付け忘れに注意。
# --- ユーザーモデルとダミーデータベース (認証用) ---
# UserMixinを継承し、Flask-Loginに必要なプロパティを追加
class User(UserMixin):
def __init__(self, id, username, password_hash):
self.id = id
self.username = username
self.password_hash = password_hash
# Flask-LoginがセッションIDとして使用するため、必ず文字列で返す
def get_id(self):
return str(self.id)
# ダミーのユーザーデータ (認証の確認のみに使用)
USER_ID_MAP = {
1: User(1, "testuser", "password123"), # ID: 1
2: User(2, "admin", "adminpass"), # ID: 2
}
USERNAME_MAP = {u.username: u for u in USER_ID_MAP.values()}
# ユーザーIDからユーザーオブジェクトをロードする関数
@login_manager.user_loader
def load_user(user_id):
"""ユーザーIDに基づいてユーザーオブジェクトを返す"""
try:
user_id_int = int(user_id)
return USER_ID_MAP.get(user_id_int)
except ValueError:
return None
ここでの説明は割愛しますが、HTMLとCSSでうまくUIを作ります。render_html()メソッドで共通のUIを先に定義しておくと楽に構成できるようです。
# --- HTML テンプレート(単一ファイル構成のためPython内で定義可能) ---
def render_html(title, body_content, current_user_status):
"""共通のHTML構造を生成する関数 (Tailwind CSSを使用)"""
tailwind_cdn = '<script src="https://cdn.tailwindcss.com"></script>'
return f"""
"""
ここからflaskアプリのルーティングを定義していきます。
# --- 6. ルーティングの定義 ---
@app.route('/')
def home():
"""公開されているホーム画面"""
is_authenticated = current_user.is_authenticated
username = current_user.username if is_authenticated else "ゲスト"
status_tag = f"""
"""
content = f"""
"""
return render_html("ホーム", content, status_tag)
@app.route('/login', methods=['GET', 'POST'])
def login_page():
"""ログイン画面とログイン処理"""
if current_user.is_authenticated:
return redirect(url_for('dashboard'))
message = ""
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
remember = bool(request.form.get('remember'))
user = USERNAME_MAP.get(username)
# 認証ロジック
if user and user.password_hash == password:
login_user(user, remember=remember)
next_page = request.args.get('next')
return redirect(next_page or url_for('dashboard'))
else:
message = '<p class="text-red-500 font-medium mb-4">ユーザー名またはパスワードが違います。</p>'
content = f"""
"""
return render_html("ログイン", content, "")
@app.route('/dashboard')
@login_required
def dashboard():
"""ログインが必須の保護されたページで、SQLAlchemyからユーザーデータを読み込む"""
user_id = int(current_user.id) # DB検索のためにint型に変換
username = current_user.username
# ★SQLAlchemyによるデータ取得/初期化★
# ユーザーのゲームデータを主キー(user_id)で検索
user_data = db.session.get(GameData, user_id)
# データが存在しない場合は、初期データを挿入する
if user_data is None:
user_data = GameData(user_id=user_id, score=0, level=1)
db.session.add(user_data)
db.session.commit()
score = user_data.score
level = user_data.level
# ★スコアをランダムに更新するデモロジックを追加★
import random
if random.random() < 0.2: # 20%の確率でスコアを増やす
user_data.score += random.randint(10, 50)
db.session.commit() # 更新をコミット
status_tag = f"""
{username} 様
<a href="{url_for('logout_page')}" class="bg-red-600 hover:bg-red-700 text-white py-1 px-3 rounded-md transition ml-4">ログアウト</a>
"""
content = f"""
"""
return render_html("ダッシュボード (SQLAlchemy連携)", content, status_tag)
@app.route('/logout')
@login_required
def logout_page():
"""ログアウト処理"""
logout_user()
return redirect(url_for('home'))
@login_manager.unauthorized_handler
def unauthorized():
"""未認証のアクセスがあった場合の処理"""
return redirect(url_for('login_page', next=request.path))
if __name__ == '__main__':
app.run(debug=True)
実装例
以上を実装したサンプルアプリをGithubで公開しています。
応用・拡張
flask_loginを導入したことで、アプリの履歴データをユーザごとに切り分けて保存することが可能になります。その場合は、DB上にlogin用テーブルの他に、履歴データのテーブルを追加することになります。