Vue.jsとFlaskでフルスタックなWebアプリの開発環境を構築 その3〜〜ログイン認証追加〜〜
はじめに
今回は、前回作成したフロントのUIがVue.js
、バックエンドのAPIがFlask
のWebアプリケーションに対してFlask-Login
を使用してログイン機能を作成します。
Flask-Login
↓前々回 kittagon.hateblo.jp
まずはFlask-Login
とログインフォームの作成に使用するFlask-WTF
を以下コマンドでインストールします。
$ pip install flask-login flask_wtf
ユーザーモデルの追加
まずはログインユーザーとなるユーザーモデルをmodels.py
に記述します。
公式サイトにも以下のように記載されており、UserクラスはUserMixin
を継承する形で記述すると簡単なようです。
To make implementing a user class easier, you can inherit from UserMixin, which provides default implementations for all of these properties and methods. (It’s not required, though.)
また、ログインフォームはとあえず簡単にflask_wtfを使いLoginForm
クラスを作成しておきます。
# backend/models.py from flask_login import UserMixin from flask_wtf import FlaskForm from wtforms import TextField, PasswordField, validators from sqlalchemy.orm import synonym from werkzeug import check_password_hash, generate_password_hash from backend import db class LoginForm(FlaskForm): email = TextField('email', validators=[validators.Required()]) password = PasswordField('Password', validators=[validators.Required()]) def __str__(self): return ''' <form action="/-/login" method="POST"> <p>%s: %s</p> <p>%s: %s</p> %s <p><input type="submit" name="submit" /></p> </form> ''' % (self.email.label, self.email, self.password.label, self.password, self.csrf_token) class User(UserMixin, db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(255), unique=True, nullable=False) email = db.Column(db.String(255), unique=True, nullable=False) _password = db.Column('password', db.String(255), nullable=False) active = db.Column(db.Boolean(), default=True) def _get_password(self): return self._password def _set_password(self, password): if password: password = password.strip() self._password = generate_password_hash(password) password_descriptor = property(_get_password, _set_password) password = synonym('_password', descriptor=password_descriptor) # フォームで送信されたパスワードの検証 def check_password(self, password): password = password.strip() if not password: return False return check_password_hash(self.password, password) # 認証処理 @classmethod def auth(cls, query, email, password): user = query(cls).filter(cls.email==email).first() if user is None: return None, False return user, user.check_password(password) class Task(db.Model): __tablename__ = 'tasks' id = db.Column(db.Integer, primary_key=True) title = db.Column(db.Text) text = db.Column(db.Text) def to_dict(self): return dict( id=self.id, title=self.title, text=self.text ) def __repr__(self): return '<Entry id={id} title={title!r}>'.format( id=self.id, title=self.title) def init(): db.create_all()
ログイン処理の追加
次にFlask側の処理の中に実際のログイン処理を記述していきます。
__init__.py
でFlask-Login
の初期化
view.py
でログイン画面のルーティングとログイン処理を実装します。
このあたりを参考。
Flask-Loginの使い方 - Qiita
Python Flaskでつくる LDAPログインページ - AAA Blog
Flaskで認証 - Kensuke Kousaka's Blog
config.py
# backend/config.py import os class BaseConfig(object): DEBUG = True SQLALCHEMY_DATABASE_URI = 'sqlite:///backend.db' # cookieを暗号化する秘密鍵 SECRET_KEY = os.urandom(24) # csrf token用秘密鍵 WTF_CSRF_SECRET_KEY = os.urandom(24)
init.py
Flask-login
を使うとcurrent_user
、login_user
、logout_user
という変数が払い出されて払い出されていい感じにログイン状態の確認ができます。
また、後述するview.py
に記載するルーティングを-
というプレフィックスをつけて取り込んでいます。
# backend/__init__.py from flask import Flask, request, redirect, url_for from flask_cors import CORS import requests from flask_sqlalchemy import SQLAlchemy from flask_login import LoginManager, current_user, login_user, logout_user, login_required # ここを追記 app = Flask('FLASK-VUE', static_folder = "./dist/static", template_folder = "./dist") app.config.from_object('backend.config.BaseConfig') db = SQLAlchemy(app) ######ここから追記###### login_manager = LoginManager() login_manager.init_app(app) from backend.models import User @login_manager.user_loader def load_user(user_id): return User.query.get(user_id) # 未認証の際のリダイレクト先を設定 @login_manager.unauthorized_handler def unauthorized_callback(): return redirect(url_for('app.login')) ############# route ############## from backend.view import view app.register_blueprint(view, url_prefix="/-") ######ここまで追記###### from backend.api import api app.register_blueprint(api, url_prefix="/api") cors = CORS(app, resources={r"/api/*": {"origins": "*"}}) @app.route('/', defaults={'path': ''}) @app.route('/<path:path>') @login_required # ここを追記 def catch_all(path): if app.debug: return requests.get('http://localhost:8080/{}'.format(path)).text return render_template("index.html")
view.py
from flask import Blueprint, redirect, request from flask_login import login_user, logout_user from backend import db from backend.models import User, LoginForm view = Blueprint('app', __name__, template_folder='templates', static_folder='templates/static') @view.route('/login', methods=['GET', 'POST']) def login(): form = LoginForm(request.form) if form.validate_on_submit(): user, authenticated = User.auth(db.session.query, form.email.data, form.password.data) if authenticated: login_user(user, remember=True) print('Login successfully.') return redirect('/tasks') else: print('Login failed.') return str(form) @view.route('/logout', methods=['POST']) def logout(): logout_user() return redirect('/login')
ログアウトボタンの追加
ログインした後のタスク画面にログアウトボタンを追加します。
以下のボタンをTasks.vue
の中に記述します。
場所はどこでも結構です。
// frontend/src/component/Tasks.vue <b-form v-if="show" action="/-/logout" method="post" > <b-button type="submit">ログアウト</b-button> </b-form>
ログインユーザーの作成
まだログインユーザーを作成する画面を作っていないので、
Pythonのインタラクティブモードからユーザーを作成し、動作確認をします。
$ python >>> from backend.models import User, db >>> User.query.all() [] >>> user = User(name='test_user', email='test@example.com') >>> user._set_password('password') >>> user <User (transient 4334868296)> >>> db.session.add(entry) >>> db.session.commit() >>> User.query.all() [<User 1>]
http://localhost:5000/-/login
を開いてログイン画面が表示され、
'test@example.com'と'password'でログインできればOKです。
また、ログインしていない状態でhttp://localhost:5000/tasks
を表示しようとすると
ログイン画面にリダイレクトされます。
ログアウトボタンでログアウトできます。
ログイン画面修正
最初に作成したのは最低限の要素のみ記述したので、もう少しきれいなレイアウトに変更します。
現状は↓のようにフォームを文字列で定義しておき、それを表示しているだけだったので、
画面をテンプレートから呼び出せるよう変更します。
def __str__(self): return ''' <form action="/-/login" method="POST"> <p>%s: %s</p> <p>%s: %s</p> %s <p><input type="submit" name="submit" /></p> </form> ''' % (self.email.label, self.email, self.password.label, self.password, self.csrf_token)
テンプレートの作成には、Vue.js側で作成している画面と揃えるために、Flask-Bootstrap
を使用します。
まずは以下コマンドでインストールします。
$ pip install flask-bootstrap
__init__.py
でflask-bootstrapの初期化をします。
# backend/__init__.py from flask import Flask, request, redirect, url_for from flask_cors import CORS import requests from flask_sqlalchemy import SQLAlchemy from flask_login import LoginManager, current_user, login_user, logout_user from flask_bootstrap import Bootstrap # ここを追記 app = Flask('FLASK-VUE', static_folder = "./dist/static", template_folder = "./dist") app.config.from_object('backend.config.BaseConfig') db = SQLAlchemy(app) bootstrap = Bootstrap(app) # ここを追記 #############以下略#############
view.py
のログイン画面表示を修正します。
テンプレートの呼び出しに加えて、action
をFlaskテンプレートの変数としてurl_for
を渡すように修正しています。
# backend/view.py from flask import Blueprint, redirect, request, render_template, url_for # render_template, url_forを追加 #############略############# @view.route('/login', methods=['GET', 'POST']) def login(): form = LoginForm(request.form) if form.validate_on_submit(): user, authenticated = User.auth(db.session.query, form.email.data, form.password.data) if authenticated: login_user(user, remember=True) print('Login successfully.') return redirect('/tasks') else: print('Login failed.') # return str(form) ## ここを修正 return render_template('login.html', form=form, action=url_for('app.login')) #############略#############
models.py
からstr要素を削除します。
また、Loginform
内に新たにsubmit要素を定義します。
# backend/models.py from flask_login import UserMixin from flask_wtf import FlaskForm from wtforms import TextField, PasswordField, validators, SubmitField # SubmitFieldの追加 from sqlalchemy.orm import synonym from werkzeug import check_password_hash, generate_password_hash from backend import db class LoginForm(FlaskForm): email = TextField('email', validators=[validators.Required()]) password = PasswordField('Password', validators=[validators.Required()]) submit = SubmitField('ログイン') # submit追加 # def __str__(self):の削除
次にログインテンプレートを作成します。
テンプレートフォルダはview.py
にて以下のように定義されています。
view = Blueprint('app', __name__, template_folder='templates', static_folder='templates/static')
view.py
と同階層にtemplates
フォルダを作成します。
app_dir ┣manage.py ┣frontend/ ┗backend/ ┣__init__.py ┣api.py ┣view.py ┣models.py ┣config.py ┗templates/
templates
フォルダ内にログイン画面用テンプレートを作成します。
<!-- backend/templates/login.html--> {% import "bootstrap/wtf.html" as wtf %} {% extends "bootstrap/base.html" %} {% block title %}This is a login page{% endblock %} {% block content %} <div class="container"> {{ wtf.quick_form(form, action=action, method="post") }} </div> {% endblock %}
またまた画面デザインは適当ですが、Bootstrap
により良い感じのフォームができました。
まとめ
これで認証までも含めたWebアプリができました。
基本的な機能はほぼ実装することができたので、今後はこれをベースにいろんな機能を実装していきたいと思います。