flask_login - Flask

  • 作成日:
  • 最終更新日:2025/06/25

flask-login とは?

ユーザー認証を簡単に扱うための Flask の拡張パッケージ。

インストール

pip install flask-login

設定

以下のコマンドを実行し、必要なパッケージをインストールします。

pip install flask sqlalchemy flask-sqlalchemy flask-wtf mysql-connector-python pymysql
  • project
    • templates
      • index.html
      • login.html
    • app.py

ファイル

app.py

from flask import Flask, render_template, request, redirect, url_for, session, flash
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.exc import NoResultFound, MultipleResultsFound
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
from flask_wtf.csrf import CSRFProtect
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, BooleanField
import bcrypt

app = Flask(__name__)

# session を使う際に SECRET_KEY を設定
app.config['SECRET_KEY'] = 'secret_key'

# CSRF 対策
csrf = CSRFProtect(app)

# データベースに MySQL を使う
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:password@localhost/sample'

# SQLALchemy を初期化
db = SQLAlchemy(app)

# User というクラス(データベースにテーブル情報)
class User(UserMixin, db.Model):
    __tablename__ = 'User'
    id = db.Column(db.Integer, primary_key=True)
    mail = db.Column(db.String(255), unique=True)
    password = db.Column(db.String(255), unique=True)

    def __init__(self, mail, password):
        self.mail = mail
        self.password = password

class LoginForm(FlaskForm):
    mail = StringField('mail')
    password = PasswordField('Password')
    remember_me = BooleanField('Remember Me')
    submit = SubmitField()

with app.app_context():
    # テーブルが存在しなければ自動で作成
    db.create_all()

    password = b"password"
    salt = b'$2b$12$ZaUSY.Z0X1sEzaEqMoKFtu'
    hashed = bcrypt.hashpw(password,salt) # パスワードのハッシュ化
    
    # DB 何もレコードがない場合 test ユーザーを作成する
    user = User.query.filter_by(mail='test').first()
    if user is None:
        testuser = User(mail='test', password=hashed)
        db.session.add(testuser)
        db.session.commit()

# <-- comment.login-manager
# flask-login の初期化
# クラスの作成
login_manager = LoginManager()
login_manager.init_app(app)
# /comment.login-manager -->

# <-- comment.login_required
# リダイレクトするビュー関数と、エラーメッセージを定義
# ビュー関数 login にリダイレクトするように指定する
login_manager.login_view = 'login'

# 未ログインユーザーにメッセージ表示
def localize_callback(*args, **kwarg):
    return 'このページにアクセスするには、ログインが必要です'

login_manager.localize_callback = localize_callback
# /comment.login_required -->


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

@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()

    # POST の処理
    if request.method == "POST":
        mail = request.form.get('mail')
        password = request.form.get('password')

        try:
            # Userテーブルから mail に一致するユーザを取得
            user = User.query.filter_by(mail=mail).one_or_none()
            
            if user is None:
                flash('ログインできませんでした。') 
                return redirect(url_for('login'))
            
            if bcrypt.checkpw(password.encode("utf8"), user.password.encode("utf8")):
                # ユーザー情報をセッションに格納
                login_user(user)
                return redirect('index')
            else:
                flash('ログインできませんでした。') 
                return redirect(url_for('login'))
        except NoResultFound:
            flash('データがありません。') 
            return redirect(url_for('login'))
        except MultipleResultsFound:
            flash('データが複数あります。') 
            return redirect(url_for('login'))
    else:
        # GET の処理
        return render_template('login.html', form = form)

# ログアウト処理
@app.route('/logout')
@login_required
def logout():
    logout_user()
    flash('ログアウトしました。') 
    return redirect(url_for('login'))

# 認証が必要なページ
@app.route('/index')
@login_required
def index():
    return render_template('index.html')

if __name__ == '__main__':
    app.run(debug=True)

login.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <ul>
    {% with messages = get_flashed_messages() %}
      {% for message in messages %}
        <li>{{ message }}</li>
      {% endfor %}
    {% endwith %}
  </ul>
  
  <form action="/login" method="post">
    <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
    <div>
      <p>mail <input type="text" name="mail"></p>
      <p>password <input type="password" name="password"></p>
      <input type="submit">
    </div>
  </form>
</body>
</html>

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <h1>flask app/index</h1>
  <p><a href="/logout">ログアウト</a></p>
  <p>{{ message }}</p>
</body>
</html>

@login_required デコレータ

login_required をインポートするには以下のようにします。

from flask_login import login_required

@login_required をつけるとログインしていない場合エラーになります。

Flask 内部での処理の順番は以下の通りです。

  • @login_manager.user_loader が呼び出される
  • @login_manager.user_loader の中で user_id の値が渡されるので、user_id をもとにユーザー情報を取得して返す
  • @login_manager.ueer_loader からのレスポンスが None の場合はログインをしていないとして @login_manager.unauthorized_handler が呼び出される

@login_required を付けることで、未ログインユーザーはログインページに転送できます。

@app.route('/index')
@login_required
def index():
    return render_template('index.html')

また、転送された際のエラーメッセージを設定することができます。

# ビュー関数 login にリダイレクトするように指定する
login_manager.login_view = 'login'

# 未ログインユーザーにメッセージ表示
def localize_callback(*args, **kwarg):
    return 'このページにアクセスするには、ログインが必要です'

login_manager.localize_callback = localize_callback

User モデル

Flask-Login 拡張機能は、アプリケーションのクラス 'User' が定義され、特定のプロパティとメソッドがアプリケーションに実装されます。

メソッド 内容
is_authenticated ユーザーが有効な資格情報を持っている場合は True、そうでない場合は False
is_active ユーザーのアカウントがアクティブな場合は True、それ以外の場合は False
is_anonymous 通常のユーザーの場合は False、ゲストユーザーの場合は True
get_id() ユーザーで重複しない識別子を文字列として返す

以下のようにすると、取得したレコードの ID が取得できます。

user = User.query.filter_by(mail=mail).one_or_none()
  print(user.get_id())

loader_user()

コールバック関数の loader_user() を定義しておく必要があります。

定義しておかないとエラーになります。

このコールバック関数は、セッションに保存されているユーザーIDからユーザーオブジェクトを読み込みするために使用します。

ユーザIDに該当するユーザが存在しない場合は、この関数が None を返します。

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