UE5 : Fondamentaux du développement web frontal, niveau 2

Séance 6 : TD : Gestion des utilisateurs

Créer et gérer des utilisateurs sur un site est courant et presque indispensable désormais. Ce TD se contentera d'apporter les bases de la gestion utilisateurs.

Installations et configurations

Afin que tout le monde réalise ce TD sur une application Flask sans erreurs et correctement configurée:

  1. Installer sur son PC l'application Seance6_base_TD
  2. Y installer un environnement virtuel Python3
  3. Installer les dépendances de l'application
  4. Télécharger la base de données factbook_users.sqlite quelque part sur son PC
  5. Remplir les variables SQLALCHEMY_DATABASE_URI, RESOURCES_PER_PAGE, SECRET_KEY, WTF_CSRF_ENABLE et PAYS_PER_PAGE dans le fichier .env

Création des utilisateurs

Comme pour les modèles de base de données ou les modèles de formulaires, les utilisateurs vont utiliser une méthode que l'on connaît déjà.

Une table users a été créée dans la base de données selon DDL suivant:

CREATE TABLE "users" (
	"id"	INTEGER NOT NULL,
	"prenom"	TEXT NOT NULL,
	"password"	TEXT NOT NULL,
	PRIMARY KEY("id" AUTOINCREMENT)
);
# models/users.py
from werkzeug.security import generate_password_hash

class Users(db.Model):
	...

	@staticmethod
    def ajout(prenom, password):
        erreurs = []
        if not prenom:
            erreurs.append("Le prénom est vide")
        if not password or len(password) < 6:
            erreurs.append("Le mot de passe est vide ou trop court")

        unique = Users.query.filter(
            db.or_(Users.prenom == prenom)
        ).count()
        if unique > 0:
            erreurs.append("Le prénom existe déjà")

        if len(erreurs) > 0:
            return False, erreurs
        
        utilisateur = Users(
            prenom=prenom,
            password=generate_password_hash(password)
        )

        try:
            db.session.add(utilisateur)
            db.session.commit()
            return True, utilisateur
        except Exception as erreur:
            return False, [str(erreur)]

Commentaires:

A propos du sel Pour fonctionner, generate_password_hash a besoin d'une variable SECRET_KEY que l'on définit dans .env et dans config.py. Cette clé secrète doit être unique, non devinable et le plus complexe possible. ATtention, ne pas changer sa valeur durant la vie de l'application, sinon tout ce qui aura été hashé par l'application deviendra introuvable.

# .env

SECRET_KEY=duselnondevinableetunique
# config.py

...
class Config():
    ...
    SECRET_KEY = os.environ.get("SECRET_KEY")

Maintenant que le modèle de users est fait et que sa méthode ajout a été développé, il faut désormais créer ce qui permettra d'exécuter cette méthode ajout, c'est à dire une route, qui renverra un formulaire permettant de saisir un prénom et un mot de passe.

Connexion

Pour gérer les utilisateurs, il existe la librairie Flask-Login, qui va permettre de savoir, très simplement, pour chaque route, si un utilisateur est connecté ou non, quel est son identifiant, etc. Il permet également de créer des sessions utilisateurs et de poser les cookies nécessaires chez le client de manière à ce que la connexion reste établie. Pour l'installer, pip install flask-login.

Comme pour Flask avec app et Flask-SQLAlchemy avec db, il va falloir instancier Flask-Login dans app.py.

# app/app.py 

from flask_login import LoginManager

...
login = LoginManager(app)

login va alors proposer 4 méthodes, dont certaines nous seront utiles:

Pour éviter à avoir à développer ces fonctionnalités de cnotre côté, Flask-Login propose le UserMixin, dont on fera hériter la classe Users:

# models/users.py

from flask_login import UserMixin

class Users(UserMixin, db.Model):
	...

Les seules méthodes de classe que nous avons à développer pour permettre à Flask-Login de fonctionner sont les suivantes:

# models/users.py
from ..app import app, db, login

class Users(UserMixin, db.Model):
	...
	def get_id(self):
        return self.id

	@login.user_loader
    def get_user_by_id(id):
        return Users.query.get(int(id))

Commentaires:

On peut désormais utiliser la gestion des utilisateurs. A nouveau, pour permettre à l'utilisateur de se connecter, il faut faire les étapes habituelles de :

La méthode statique Users.identification(prenom, mot_de_passe) permettra d'identifier l'utilisateur grâce au prénom et au mot de passe qu'il fournira dans le formulaire de connexion.

# models/users.py

from werkzeug.security import check_password_hash

class Users(UserMixin, db.Model):
    ...

    @staticmethod
    def identification(prenom, password):
        utilisateur = Users.query.filter(Users.prenom == prenom).first()
        if utilisateur and check_password_hash(utilisateur.password, password):
            return utilisateur
        return None

Commentaires:

Aides:

Déconnexion

La déconnexion est très simple avec Flask-Login, elle tient en une route et en une bonne configuration de la barre de navigation en HTML.

# routes/users.py
from flask_login import  current_user, logout_user

@app.route("/utilisateurs/deconnexion", methods=["POST", "GET"])
def deconnexion():
    if current_user.is_authenticated is True:
        logout_user()
    flash("Vous êtes déconnecté", "info")
    return redirect(url_for("accueil"))

Commentaires:

Pour une meilleure expérience utilisateur, il convient de cacher le lien de déconnexion quand il n'est pas connecté, et d'afficher le lien de connexion quand il n'est pas connecté.

<!-- partials/conteneur.html -->

{% if current_user.is_authenticated %}
<a class="dropdown-item" href="{{ url_for('deconnexion') }}">Se déconnecter</a>
{% else %}
<a class="dropdown-item" href="{{ url_for('connexion') }}">Se connecter</a>
{% endif %}

Rendre obligatoire la connexion sur une route

Imaginons que l'on souhaite rendre la recherche avancée dans le Factbook payante. Il faudrait alors restreindre l'accès à cette page uniquement aux utilisateurs ayant payé. Pour cela, il ne suffit que d'une ligne de code dans les routes, en utilisant le décorateur @login_required.

# routes/generales.py

from flask_login import login_required

@app.route("/recherche", methods=['GET', 'POST'])
@app.route("/recherche/<int:page>", methods=['GET', 'POST'])
@login_required
def recherche(page=1):
	...

Si un utilisateur non connecté arrive veut accéder à la route /recherche, il sera alors redirigé vers la page de connexion (indiquée à Flask avec login.login_view = 'connexion' dans routes/users.py) : http://localhost:5000/utilisateurs/connexion?next=%2Frecherche.