iMind Developers Blog

iMind開発者ブログ

Flask-Loginを用いたログイン機能の実装

概要

Flask-Loginを用いて簡易なログイン機能を実装する。

具体的に実装される機能は下記あたり。

  • 入力されたユーザー名とパスワードでログイン
  • ログアウト
  • ログインしていなければログインページにリダイレクト

user/passwordをDB等と照合するような処理は割愛し、ハードコーディングでscott/tigerであればログインできるよう記述する。

バージョン情報

  • Flask==1.1.1
  • Flask-Login==0.4.1
  • Flask-WTF==0.14.2

今回実装するコードのスケルトン

今回実装する機能はログイン、ログアウト、トップページ(ログイン不要)、会員ページ(要ログイン)の4ページ。

import flask

app = flask.Flask(
        __name__,
        template_folder='templates')

@app.route('/login', methods=['GET', 'POST'])
def login():
    ''' ログイン '''

@app.route('/logout', methods=['GET'])
def logout():
    ''' ログアウト '''

@app.route('/', methods=['GET'])
def index():
    ''' トップページ(ログイン不要) '''
    
@app.route('/member', methods=['GET'])
def member():
    ''' 会員ページ(要ログイン) '''

LoginManagerの初期化

Flask-LoginはLoginManagerというクラスをappに設定して使います。

import flask_login

app = flask.Flask(
        __name__,
        template_folder='templates')

app.secret_key = b'jugemjugemgokonosurikirekaijarisuigyono'
login_manager = flask_login.LoginManager()
login_manager.init_app(app)

ログアウトページの実装

1番簡単なログアウト機能から実装。

ログアウトは flask_loign.logout_user() を実行するだけ。

import flask_login

@app.route('/logout', methods=['GET'])
def logout():
    ''' ログアウト '''
    flask_login.logout_user()
    return 'ログアウトしました'

これで http://localhost:5000/logout を踏めばログアウトできます。

callbackの実装

LoginManagerにはいくつかのcallbackを設定できるようになっています。

name description
user_loader セッションからユーザーをリロードする
request_loader Flaskのrequestからユーザーをロードする
unauthorized_handler ログイン必須ページで認証が確認できなかった場合の処理
needs_refresh_handler fresh_login_required(cookieからではない認証済み)指定ページで未認証の場合

user_loaderはcookieからセッションのチェックを行い、request_loaderはflask.requestからチェックを行います。一般的なアプリではuser_loaderを使うことが多そうです。

ログインしたユーザーの情報はUserMixinクラスで管理されます。

ここでは user_id を保持しておくだけのシンプルなUserMixinクラスの実装を作り、user_loaderにはそれを返す処理を実装します。

class User(flask_login.UserMixin):
    def __init__(self, user_id):
        self.id = user_id

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

user_loaderはログインが必要なページに行くたびに呼ばれるので、ここに各種の独自認証処理も追加できます(上記処理ではセッションさえあればノーチェック)。

ログイン必須ページの実装

ログインが不要な各種リンクが付いたトップページを作ります。

@app.route('/', methods=['GET'])
def index():
    ''' トップページ(ログイン不要) '''
    return '''<h1>top page</h1><p><a href="/login">ログイン</a><p><a href="/member">メンバー</a><p><a href="/logout">ログアウト</a>'''

f:id:imind:20200121195700p:plain

続いてログインが必須なmemberページを作ります。 @flask_login.login_requiredを指定するとログイン必須になります。

@app.route('/member', methods=['GET'])
@flask_login.login_required
def member():
    ''' 会員ページ(要ログイン) '''
    return '会員ページ'

まだログイン機能は実装していないので、この状況で /member を開いても401エラーになります。

f:id:imind:20200121195754p:plain

ログインしていない場合はログインページに飛ぶように、unauthorized_handlerを実装しておきます。

@login_manager.unauthorized_handler
def unauthorized():
    return flask.redirect('/login')

リダイレクトしてからログインされた後、リダイレクト前のページに戻したい場合は、redirect先を /login?next=' + request.path とかにしておいてログイン後に指定されたパスに飛ぶ処理を実装します。

ログインページの実装

ログインページのPython側を実装します。HTMLはtemplateで別出し。

flask_wtfでuser_idとpasswordの2つを保持するフォームを作り、 /login が呼ばれたらIDとパスワードを確認して flask_login.login_user でログインを実行しています。

flask_login.login_userに渡す引数は user_loader のところで作ったUserクラスです。

import flask_wtf
import wtforms

class LoginForm(flask_wtf.FlaskForm):
    ''' ログインフォーム '''
    user_id = wtforms.StringField(
            'user_id',
            [wtforms.validators.DataRequired(), wtforms.validators.Length(min=3, max=20)])
    password = wtforms.PasswordField(
            'password',
            [wtforms.validators.DataRequired(), wtforms.validators.Length(min=4, max=20)])

@app.route('/login', methods=['GET', 'POST'])
def login():
    ''' ログイン '''
    form = LoginForm(flask.request.form)

    # validationチェック
    if form.validate_on_submit():
        # 通常はDB等で認証しますが割愛してハードコーディング
        if form.user_id.data == 'scott' and form.password.data == 'tiger':
            # ログインの実行
            user = User(form.user_id.data)
            flask_login.login_user(user)
            # ログインに成功したらmemberページへ飛びます
            return flask.redirect('/member')
        else:
            # ここにログイン失敗のメッセージを書いたりする
            pass

    # GET時やログイン
    return flask.render_template(
            'login.html',
            form=form)

Python側で呼んでいるlogin.htmlテンプレートの記述。

<!-- エラーメッセージの表示 -->
{% for field, errors in form.errors.items() %}
  <p style="color:red">{{ form[field].label }}: {{ ', '.join(errors) }}
{% endfor %}

<!-- ログインフォーム -->
<form id="login-form" method="post">
  {{ form.csrf_token }}
  <p><label for="user_id">user id</label>
  {{ form.user_id }}
  <p><label for="password">password</label>
  {{ form.password }}
  <p><button type="submit">sign in</button>
</form>

できた。

f:id:imind:20200121195836p:plain

ここに scott/tigerを入力してログインするとメンバーページが表示されます。

サンプルコード全文

https://github.com/imind-inc/blog/blob/master/flask-login-example/

改定履歴

Author: Masato Watanabe, Date: 2020-01-25, 記事投稿