====== Flask-Login ======
===== Installation =====
pip install flask-login
===== Classe User =====
La classe User de l'application doit implémenter quelques propriétés
et méthodes, pour cela le plus simple est de la faire hériter de
**UserMixin** :
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key = True)
email = db.Column(db.String(64), unique=True, index=True)
username = db.Column(db.String(64), unique=True, index=True)
password_hash = db.Column(db.String(128))
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
# ...
La classe **UserMixin** inclut les propriétés et méthodes nécessaires :
* ''is_authenticated''
* ''is_active''
* ''is_anonymous''
* ''get_id()''
Implémenter la propriété **password** qui sera en **écriture seule**,
en effet on peut seulement écrire un nouveau password mais jamais le
relire. L'écriture du password assigne le **hash** de la valeur donnée
au champ **password_hash** :
@property
def password(self):
raise AttributeError('password is not a readable attribute')
@password.setter
def password(self, password):
self.password_hash = generate_password_hash(password)
Implémenter la méthode **verify_password** :
def verify_password(self, password):
return check_password_hash(self.password_hash, password)
===== Initialisation =====
Initialiser le **LoginManager** lors de la création de l'application :
from flask import Flask
from flask_login import LoginManager
app = Flask(__name__)
# ...
login_manager = LoginManager()
login_manager.login_view = 'auth.login'
login_manager.init_app(app)
# ...
Dans la propriété **login_view**, on indique la route vers la page
de connexion, vers laquelle sera redirigé un utilisateur tentant d'accéder
à une page protégée.
Enfin, il faut définir une fonction qui sera appelée lorsque **Flask-Login**
aura besoin de charger un utilisateur sur base de son identifiant.
# ...
from app.models.user import User
# ...
@login_manager.user_loader
def load_user(user_id):
# Lecture de l'utilisateur depuis la base de données
# L'identifiant est passé sous forme de chaîne, on le
# transforme en int.
return User.query.get(int(user_id))
===== Formulaire de login =====
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired, Length, Email
class LoginForm(FlaskForm):
email = StringField('Email',
validators=[DataRequired(), Length(1, 64), Email()]
password = PasswordField('Password',
validators=[DataRequired()])
remember_me = BooleanField('Keep le logged in')
submit = SubmitField('Log In')
===== Login =====
from flask import Blueprint
from flask import request
from flask import render_template, redirect, url_for, flash
from flask_login import login_user
from app.forms.auth import LoginForm
from app.models.user import User
auth = Blueprint('auth', __name__)
@auth.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
# Si le formulaire a été posté (submit) et que son contenu
# a passé toutes les validations
if form.validate_on_submit():
# Si l'utilisateur a cliqué sur "Login"
if form.submit.data:
# Rechercher l'utilisateur selon l'email entré
user = User.query.filter_by(email = form.email.data).first()
# Si l'utilisateur est trouvé, et que son mot de passe correspond
if user is not None and user.verify_password(form.password.data):
# Prendre en compte la connexion de l'utilisateur.
# Si 'remember_me' vaut True, un cookie sera ajouté afin de
# conserver la connexion active.
login_user(user, form.remember_me.data)
flash(u"Welcome, %s !" % user.name, "success")
# Retourner une redirection
# - soit vers la page 'suivante' si le login provient d'une
# tentative d'accès à une page protégée
# - soit vers la page d'accueil
next = request.args.get('next')
if next is None or not next.startswith('/'):
next = url_for('main.home')
return redirect(next)
# Si l'utilisateur n'est pas trouvé, on ajoute un message flash
# avant de retourner au formulaire
flash(u"Invalid username or password.", "error")
# Rendu du formulaire dans tous les cas non traités ci-dessus
return render_template('auth/login.html', form = form)
===== Logout =====
from flask_login import logout_user, login_required
@auth.route('/logout')
@login_required
def logout():
logout_user()
flash(u"You have been logged out.", "success")
return redirect(url_for('main.home'))
===== Objet current_user =====
La variable ''current_user'' est définie par **Flask-Login**,
et elle disponible dans les templates et dans les vues.
Lorsqu'aucun utilisateur n'est connecté, ''current_user'' contient
un objet dont la propriété ''is_authenticated'' vaut **False**
Lorsqu'un utilisateur est connecté, ''current_user'' contient un
objet de la classe **User**.
from flask_login import current_user
# ...
if current_user.is_authenticated:
print("Hello %s !" % current_user.name)
On peut afficher le lien **Log In** ou **Log Out** en fonction
de l'état de l'utilisateur (connecté ou non):
{% if current_user.is_authenticated %}
Log Out
{% else %}
Log In
{% endif %}
===== Protection d'une route =====
Avec le décorateur **@login_required**
:!: Il est important de mettre les deux décorateurs dans cet ordre.
from flask_login import login_required
@app.route('/secret')
@login_required
def secret():
return 'Seuls les utilisateurs authentifiés verront ceci.'