今回は、Flask-Login Library と SQLAlchemy を使って Flask のユーザ認証をコーディングします。
現在、ほとんどのWebサイトにはユーザー認証の仕組みが組み込まれています。
ユーザー認証のアカウントは、直接設定することもできますし、Google や Facebook、Apple などのサードパーティを経由して設定することもできます。
Appleなど。
典型的なユーザーログインのページは、こんな感じです。
pip install flask-login
 | 
ユーザー認証は、特定のユーザーだけがアクセスできるようにユーザーデータを保護するため、ウェブページの重要な部分です。
ユーザーを認証するには、さまざまな方法があります。
- 
- クッキーベースの認証
 
 - トークンによる認証
 - OAuth認証
 - OpenId
 - Saml
 
これからFlask-Login認証を使ってコーディングしていきます。
では、コーディングの部分に飛び込んでみましょう。
この記事もチェック:Flaskでフォームを実装|ユーザーの入力を受け取る方法などを解説
Flask ユーザー認証のハンズオン
Flask-login は、Cookie ベースの認証を使っています。
クライアントが認証情報を使ってログインすると、Flask はユーザー ID を含むセッションを作成し、そのセッション ID をクッキーでユーザーに送ります。
まず最初に、Flask-Loginをインストールする必要があります。
from werkzeug.security import generate_password_hash
a = generate_password_hash('1234')
print(a)
 | 
インストールが完了したら、いよいよコーディングに入ります。
この記事もチェック:FlaskでのMySQLの使い方|インストールや接続を解説する
1. models.py ファイルのコーディング
まず、ユーザの認証情報を保存するためのUser Modelを作成します。
このために、Flask_SQLAlchemyとSQLiteデータベースを使用します。
ここで注意すべき重要な点は、ユーザーのパスワードを直接データベースに保存できないことです。
なぜなら、もしハッカーがサイトにアクセスした場合、データベースからすべての情報を取得できてしまうからです。
というのも、もしハッカーがサイトにアクセスした場合、データベースからすべての情報を取得できてしまうからです。
しかし、Flask werkzeug にはこの問題に対処するための機能が組み込まれているので、心配はいりません。
この記事もチェック:FlaskのCookiesの設定、保存や取得等の使い方を分かりやすく解説する
1.1 パスワードハッシュを設定する
パスワードハッシュを使用することで解決します。
ハッシュとは何か、ターミナルでPythonシェルを開き、次のコマンドを実行します。
from werkzeug.security import generate_password_hash, check_password_hash
a = generate_password_hash('1234')
print(a)
chech_password_hash(a,'1234')
 | 
以下のような長いランダムな文字列が得られます。
pip install flask-sqlalchemy
 | 
したがって、ハッカーがアクセスしたとしても、復号化することはできません。
また、Hashとパスワードを比較するために、check_password_hashという関数も用意されています。
これは以下のように動作します。
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import UserMixin
db = SQLAlchemy()
class UserModel(UserMixin, db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(80), unique=True)
    username = db.Column(db.String(100))
    password_hash = db.Column(db.String())
    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)
 | 
マッチしたらTrue、マッチしなかったらFalseを返します。
from flask_login import LoginManager
#...login = LoginManager()
#... | 
1.2 データベースにハッシュ化されたパスワードを追加する
また、FlaskSQLAlchemy を持っていない場合は、pip コマンドでインストールするだけです。
from flask_login import LoginManager
login = LoginManager()
@login.user_loader
def load_user(id):
    return UserModel.query.get(int(id))
 | 
SQLAlchemy がインストールできたら、models.py を作成して、以下のコードを追加してください。
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import LoginManager
login = LoginManager()
db = SQLAlchemy()
class UserModel(UserMixin, db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(80), unique=True)
    username = db.Column(db.String(100))
    password_hash = db.Column(db.String())
    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)
@login.user_loader
def load_user(id):
    return UserModel.query.get(int(id))
 | 
ここで
- email、username、password のハッシュを保存しています。
 - また、パスワードハッシュを生成するためのset_passwordと、それらを比較するためのcheck_passwordという2つのクラスメソッドを定義します。
 
また、flaskのintek_loginライブラリのUserMixinを使用しています。
UserMixinは後で使用するいくつかの組み込み関数を持っています。
- isÂi_authenticated: ユーザーが有効なクレデンシャルを持っている場合にTrueを返す
 - isanthus_active: ユーザーのアカウントがアクティブであればTrueを返す。インスタグラムで無効化されたアカウントはすべてFalseを返します。
 - is_anonymous: 匿名。レギュラーユーザーにはFalseを、ファーストタイマー/匿名ユーザーにはTrueを返す。
 - get_id(): ユーザーの一意な識別子を文字列で返す。
 
この記事もチェック:Flaskのライブラリflask-wtfの使い方|簡単にフォームを作成するよ
1.3. Flask_login エクステンションの設定
また、Flaskの拡張機能であるFlask_loginを作成し、初期化する必要があります。
コードで行います。
from flask import Flask
  app =Flask(__name__)
  app.run(host='localhost', port=5000)
 | 
先ほど説明したように、FlaskはログインしたユーザのUser IDをセッションに保存しています。
Flask_Loginはデータベースについて何も知らないので、両者をリンクさせる関数を作成する必要があります。
これはuser_loader関数を使用して行います。
構文は以下の通りです。
from flask import Flask
  app =Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///<db_name>.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
  app.run(host='localhost', port=5000)
 | 
この記事もチェック:Flaskの便利な拡張機能(Extension)を紹介していく
1.4. コードの完成
models.pyの部分は以上です。
一度、コード全体を見てみましょう。
from flask import Flask
from models import db
app =Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///<db_name>.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
  db.init_app(app)app.run(host='localhost', port=5000)
 | 
FlaskのSQLAlchemyに慣れていない方は、SQLAlchemyの記事もご覧ください。
2. Flask のメインアプリケーションファイルをコーディングする
では、メインの Flask アプリケーションファイルをコーディングしてみましょう。
from flask import Flask
from models import db
  app =Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///<db_name>.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
  db.init_app(app)@app.before_first_request
def create_table():
    db.create_all()
app.run(host='localhost', port=5000)
 | 
2.1 データベースと Flask ファイルをリンクする
さて、SQLALchemy と SQLite データベースをリンクする必要があります。
そのために、次のコードを追加します。
from flask import Flask
from models import login
app =Flask(__name__)
login.init_app(app)app.run(host='localhost', port=5000)
 | 
を好きな名前に置き換えるだけです。
また、SQLAlchemy の DB インスタンス(models.py にあります)を、メインアプリにリンクさせる必要があります。
そのために、以下を追加します。
from flask import Flask
from models import login
  app =Flask(__name__)
login.init_app(app)login.login_view = 'login'
app.run(host='localhost', port=5000)
 | 
次に、最初のユーザリクエストの前に、データベースファイルを作成するコードを追加します。
これは以下のように行います。
from flask import Flask, render_template
from flask_login import login_required
@app.route('/blogs')
@login_requireddef blog():
    return render_template('blog.html')
 | 
これでDBの部分はすべて終わりました。
では、Flask_loginの部分に移りましょう。
この記事もチェック:Flaskにおけるアプリケーションコンテキストとリクエストコンテキストを解説する
2.2 アプリにユーザー認証を追加する
DB インスタンスと同様に、ログインインスタンスもアプリにリンクする必要があります。
これを行うには、以下を使用します。
<h2>Welcome to the Blog</h2>
<h3>Hi {{ current_user.username }}</h3>
<a href="{{ url_for('logout')}}">Logout Here</a>
 | 
その後、Flask_loginに、未認証のユーザがリダイレクトされるページについて伝えます。
したがって、コードを追加します。
from flask import Flask, request, render_template
from flask_login import current_user, login_user
@app.route('/login', methods = ['POST', 'GET'])
def login():
    if current_user.is_authenticated:
        return redirect('/blogs')
         if request.method == 'POST':
        email = request.form['email']
        user = UserModel.query.filter_by(email = email).first()
        if user is not None and user.check_password(request.form['password']):
            login_user(user)
            return redirect('/blogs')
         return render_template('login.html')
 | 
リダイレクトページを指定したら、認証が必要なすべてのウェブページビューに @login_required デコレーターを追加するだけでよいです。
かっこいい! これで、ログイン、登録、ログアウトのビューだけが残りました。
しかし、その前に、認証後にユーザーが見ることができるシンプルなページをコーディングしてみましょう。
2.3 シンプルなビューのコード化
それでは、簡単なビューを追加してみましょう。
<form action="" method = "POST">
    <p>email <input type = "email" name = "email" /></p>
    <p>password <input type = "password" name = "password" /></p>
    <p> submit <input type = "submit" value = "Submit" /></p>
</form>
<h3>Dont Have an account??</h3>
<h3><a href = "{{url_for('register') }}">Register Here</a></h3>
 | 
ここでは @login_required デコレーターを使用していることに注意しましょう。
blog.html のテンプレートは以下のようになります。
from flask import Flask, request, render_template
from flask_login import current_user
@app.route('/register', methods=['POST', 'GET'])
def register():
    if current_user.is_authenticated:
        return redirect('/blogs')
         if request.method == 'POST':
        email = request.form['email']
        username = request.form['username']
        password = request.form['password']
        if UserModel.query.filter_by(email=email):
            return ('Email already Present')
                     user = UserModel(email=email, username=username)
        user.set_password(password)
        db.session.add(user)
        db.session.commit()
        return redirect('/login')
    return render_template('register.html')
 | 
テンプレートについてもっと知りたい方は Flask Templates の記事を参照してください。
2.3 ログインビューのコード化
ログインビューはシンプルなものになります。
それは以下のことを行う必要があります。
- ユーザがすでに認証されている場合、blogページにリダイレクトするか、HTMLフォームを表示します。
 - DBからユーザ情報を取得します。
 - 認証情報を比較し、正しければ、blogs ページにリダイレクトする
 
というわけで、コードを追加します。
<form action="" method = "POST">
    <p>email <input type = "email" name = "email" /></p>
    <p>Username <input type = "text" name = "username" /></p>
    <p>password <input type = "password" name = "password" /></p>
    <p> submit <input type = "submit" value = "Submit" /></p>
</form>
<h3>Already Have an Account?</h3><br>
<h3><a href ="{{url_for('login')}}">Login Here</a></h3>
 | 
そして、login.html テンプレートを追加します。
from flask import Flask, render_template
from Flask_login import logout_user
@app.route('/logout')
def logout():
    logout_user()
    return redirect('/blogs')
 | 
2.4 レジスタビューのコード化
Register View は、以下の機能を備えている必要があります。
- ユーザーがすでに認証されている場合、blog ページにリダイレクトするか、HTML フォームを表示します。
 - ユーザーのデータを DB に追加する
 - ログインページへのリダイレクト
 
というわけで、コードは以下のようになります。
from flask import Flask,render_template,request,redirect
from flask_login import login_required, current_user, login_user, logout_user
from models import UserModel,db,login
app = Flask(__name__)
app.secret_key = 'xyz'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///data.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app)login.init_app(app)login.login_view = 'login'
@app.before_first_request
def create_all():
    db.create_all()
     @app.route('/blogs')
@login_requireddef blog():
    return render_template('blog.html')
@app.route('/login', methods = ['POST', 'GET'])
def login():
    if current_user.is_authenticated:
        return redirect('/blogs')
         if request.method == 'POST':
        email = request.form['email']
        user = UserModel.query.filter_by(email = email).first()
        if user is not None and user.check_password(request.form['password']):
            login_user(user)
            return redirect('/blogs')
         return render_template('login.html')
@app.route('/register', methods=['POST', 'GET'])
def register():
    if current_user.is_authenticated:
        return redirect('/blogs')
         if request.method == 'POST':
        email = request.form['email']
        username = request.form['username']
        password = request.form['password']
        if UserModel.query.filter_by(email=email).first():
            return ('Email already Present')
                     user = UserModel(email=email, username=username)
        user.set_password(password)
        db.session.add(user)
        db.session.commit()
        return redirect('/login')
    return render_template('register.html')
@app.route('/logout')
def logout():
    logout_user()
    return redirect('/blogs')
 | 
したがって、register.htmlページは次のようになります。
python filename.py | 
2.5 ログアウトビューのコード化
ログアウトビューは、単純にユーザーをログアウトさせるだけです。
そのため、以下のコードを追加します。
これで終わりです! それでは、このセクションの全コードを見てみましょう。

UserModel.query.filter_by(email=email).first() は、データベースから取得した最初のユーザーを返すか、ユーザーが見つからない場合は None を返します。
Flask ユーザー認証アプリケーションの実装
最後にアプリをテストしてみましょう。
Flaskのファイルを実行します。

そして、”/blogs “にアクセスしてみてください。
ログインページにリダイレクトされます。

registerをクリックし、あなたの情報を追加します。

送信をクリックすると、ログインページに戻ります。
今回は認証情報を入力し、ログインしてください。
ブログのページが表示されます。

注意: a@gmail.com のような簡単なメールを使用すると、Chromeブラウザで以下のようなエラーが発生する場合があります。

このように、”blogs” エンドポイントにリダイレクトされました。
上のスクリーンショットでは、非常に弱いパスワードでランダムな非実在を使用したため、セキュリティメッセージがポップアップしました。
より強力なパスワードと適切なメールアドレスを使用して同じことを試せば、この場合のようなセキュリティ警告ではなく、直接ブログのページが表示されるようになります。
この記事もチェック:Flaskのエラー処理|エラーメッセージの表示やエラーログを表示させる方法
まとめ
以上です。
以上、Flask のユーザー認証についてでした。
Flask のセッションとクッキーがどのように動作するかについては、 Flask のセッションとクッキーの記事を参照してください。
次回は、アプリケーションをクラウドサーバにデプロイします。