FlaskとSQLAlchemyを使ってログイン認証機能を実装する方法

スポンサーリンク

今回は、Flask-Login Library と SQLAlchemy を使って Flask のユーザ認証をコーディングします。

現在、ほとんどのWebサイトにはユーザー認証の仕組みが組み込まれています。

ユーザー認証のアカウントは、直接設定することもできますし、Google や Facebook、Apple などのサードパーティを経由して設定することもできます。

Appleなど。

典型的なユーザーログインのページは、こんな感じです。

pip install flask-login

ユーザー認証は、特定のユーザーだけがアクセスできるようにユーザーデータを保護するため、ウェブページの重要な部分です。

ユーザーを認証するには、さまざまな方法があります。

    1. クッキーベースの認証
  1. トークンによる認証
  2. OAuth認証
  3. OpenId
  4. Saml

これからFlask-Login認証を使ってコーディングしていきます。

では、コーディングの部分に飛び込んでみましょう。

スポンサーリンク

Flask ユーザー認証のハンズオン

Flask-login は、Cookie ベースの認証を使っています。

クライアントが認証情報を使ってログインすると、Flask はユーザー ID を含むセッションを作成し、そのセッション ID をクッキーでユーザーに送ります。

まず最初に、Flask-Loginをインストールする必要があります。

from werkzeug.security import generate_password_hash
a = generate_password_hash('1234')
print(a)

インストールが完了したら、いよいよコーディングに入ります。

1. models.py ファイルのコーディング

まず、ユーザの認証情報を保存するためのUser Modelを作成します。

このために、Flask_SQLAlchemyとSQLiteデータベースを使用します。

ここで注意すべき重要な点は、ユーザーのパスワードを直接データベースに保存できないことです。

なぜなら、もしハッカーがサイトにアクセスした場合、データベースからすべての情報を取得できてしまうからです。

というのも、もしハッカーがサイトにアクセスした場合、データベースからすべての情報を取得できてしまうからです。

しかし、Flask werkzeug にはこの問題に対処するための機能が組み込まれているので、心配はいりません。

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(): ユーザーの一意な識別子を文字列で返す。

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)

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_required
def blog():
    return render_template('blog.html')

これでDBの部分はすべて終わりました。

では、Flask_loginの部分に移りましょう。

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_required
def 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 ログアウトビューのコード化

ログアウトビューは、単純にユーザーをログアウトさせるだけです。

そのため、以下のコードを追加します。

Django User Authentication - Allow Signup and Login Using Django - AskPython
User Login Page

これで終わりです! それでは、このセクションの全コードを見てみましょう。

Password Hash
Password Hash

UserModel.query.filter_by(email=email).first() は、データベースから取得した最初のユーザーを返すか、ユーザーが見つからない場合は None を返します。

Flask ユーザー認証アプリケーションの実装

最後にアプリをテストしてみましょう。

Flaskのファイルを実行します。

Password Hash
Password Hash

そして、”/blogs “にアクセスしてみてください。

ログインページにリダイレクトされます。

Login
Login

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

Register
Register

送信をクリックすると、ログインページに戻ります。

今回は認証情報を入力し、ログインしてください。

ブログのページが表示されます。

Blogs
Blogs

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

Redirect To Blog Successful Login
Redirect To Blog Successful Login

このように、”blogs” エンドポイントにリダイレクトされました。

上のスクリーンショットでは、非常に弱いパスワードでランダムな非実在を使用したため、セキュリティメッセージがポップアップしました。

より強力なパスワードと適切なメールアドレスを使用して同じことを試せば、この場合のようなセキュリティ警告ではなく、直接ブログのページが表示されるようになります。

まとめ

以上です。

以上、Flask のユーザー認証についてでした。

Flask のセッションとクッキーがどのように動作するかについては、 Flask のセッションとクッキーの記事を参照してください。

次回は、アプリケーションをクラウドサーバにデプロイします。

タイトルとURLをコピーしました