Authentification dans Flask
Pourquoi Authentification dans Flask ?
L'authentification est un élément crucial de toute application Web, que ce soit une simple plateforme de gestion des tâches ou une grande entreprise. Elle permet de contrôler qui accède aux ressources et les fonctionnalités de l'application. Dans le contexte professionnel, un développeur a besoin d'authentification pour :
- Protéger les données sensibles
- Assurer la sécurité des utilisateurs
- Gérer les droits d'accès et les permissions
Un cas concret est une application de gestion de projet où chaque utilisateur doit être authentifié avant de pouvoir consulter, modifier ou ajouter des tâches. L'authentification permet de s'assurer que seuls les membres autorisés peuvent effectuer ces opérations.
Prerequis
- Connaissance de base de Flask
- Connaissances en Python
- Installation d'un environnement de développement (Python, pip)
- Un éditeur de texte ou un IDE
Concepts fondamentaux
1. Utilisateurs et Mot de Passe
Chaque utilisateur a besoin d'un nom d'utilisateur et d'un mot de passe pour s'authentifier. Flask utilise des extensions comme flask-security qui gèrent automatiquement les utilisateurs et les mots de passe.
from flask import Flask
from flask_security import Security, SQLAlchemyUserDatastore, UserMixin, RoleMixin
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'
app.config['SECURITY_PASSWORD_SALT'] = 'your_secret_salt'
db = SQLAlchemy(app)
class Role(db.Model, RoleMixin):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
description = db.Column(db.String(255))
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True)
password = db.Column(db.String(255))
active = db.Column(db.Boolean())
roles = db.relationship('Role', secondary='roles_users',
backref=db.backref('users', lazy='dynamic'))
roles_users = db.Table('roles_users',
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore)
2. Gestion des Sessions
Flask utilise les sessions pour stocker les informations de l'utilisateur une fois qu'il est connecté. Les sessions sont sécurisées avec un secret clé.
app.secret_key = 'your_secret_key'
3. Routes Protégées
Pour certaines routes, il faut s'assurer que l'utilisateur est connecté avant d'accéder à ces ressources. Flask-Login facilite ce processus.
from flask_login import LoginManager, login_required, current_user
login_manager = LoginManager()
login_manager.init_app(app)
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
@app.route('/dashboard')
@login_required
def dashboard():
return f"Welcome {current_user.email}"
Mise en pratique : projet fil rouge
Nous allons créer un mini-projet complet : une application de gestion des tâches. L'application permettra aux utilisateurs de s'enregistrer, se connecter et ajouter/modifier/supprimer des tâches.
Étape 1 : Installation des dépendances
pip install Flask Flask-SQLAlchemy Flask-Login Flask-Security
Étape 2 : Structure du projet
task_manager/
├── app.py
├── models.py
├── templates/
│ ├── base.html
│ ├── register.html
│ ├── login.html
│ └── dashboard.html
└── static/
└── style.css
Étape 3 : Configuration de l'application
## app.py
from flask import Flask, render_template, redirect, url_for, request
from models import db, User, Role, user_datastore
from flask_security import Security, login_required, current_user
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///tasks.db'
app.config['SECRET_KEY'] = 'your_secret_key'
app.config['SECURITY_PASSWORD_SALT'] = 'your_secret_salt'
db.init_app(app)
security = Security(app, user_datastore)
@app.route('/')
def index():
return redirect(url_for('dashboard'))
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
@app.route('/register', methods=['GET', 'POST'])
@security.auth_required('anonymous')
def register():
if request.method == 'POST':
user_data = {
'email': request.form['email'],
'password': request.form['password']
}
try:
user_datastore.create_user(**user_data)
db.session.commit()
return redirect(url_for('login'))
except Exception as e:
print(e)
return render_template('register.html')
@app.route('/login', methods=['GET', 'POST'])
@security.auth_required('anonymous')
def login():
if request.method == 'POST':
email = request.form['email']
password = request.form['password']
user = User.query.filter_by(email=email).first()
if user and user.password == password:
return redirect(url_for('dashboard'))
else:
return "Invalid credentials"
return render_template('login.html')
@app.route('/dashboard')
@login_required
def dashboard():
return render_template('dashboard.html', user=current_user)
Étape 4 : Création des modèles
## models.py
from flask_sqlalchemy import SQLAlchemy
from flask_security import UserMixin, RoleMixin
db = SQLAlchemy()
class Role(db.Model, RoleMixin):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
description = db.Column(db.String(255))
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True)
password = db.Column(db.String(255))
active = db.Column(db.Boolean())
roles = db.relationship('Role', secondary='roles_users',
backref=db.backref('users', lazy='dynamic'))
roles_users = db.Table('roles_users',
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))
Étape 5 : Création des templates
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Task Manager</title>
<link rel="stylesheet" href="url_for('static', filename='style.css')">
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>
<!-- templates/register.html -->
{% extends 'base.html' %}
{% block content %}
<h2>Register</h2>
<form method="post">
<input type="email" name="email" placeholder="Email" required>
<input type="password" name="password" placeholder="Password" required>
<button type="submit">Register</button>
</form>
{% endblock %}
<!-- templates/login.html -->
{% extends 'base.html' %}
{% block content %}
<h2>Login</h2>
<form method="post">
<input type="email" name="email" placeholder="Email" required>
<input type="password" name="password" placeholder="Password" required>
<button type="submit">Login</button>
</form>
{% endblock %}
<!-- templates/dashboard.html -->
{% extends 'base.html' %}
{% block content %}
<h2>Welcome user.email</h2>
<a href="/logout">Logout</a>
{% endblock %}
Erreurs frequentes et debugging
1. Erreur : UnboundLocalError: local variable 'user_datastore' referenced before assignment
Code incorrect :
@app.route('/register', methods=['GET', 'POST'])
@security.auth_required('anonymous')
def register():
if request.method == 'POST':
user_data = {
'email': request.form['email'],
'password': request.form['password']
}
try:
user_datastore.create_user(**user_data)
db.session.commit()
return redirect(url_for('login'))
except Exception as e:
print(e)
return render_template('register.html')
Code correct :
@app.route('/register', methods=['GET', 'POST'])
@security.auth_required('anonymous')
def register():
if request.method == 'POST':
user_data = {
'email': request.form['email'],
'password': request.form['password']
}
try:
user_datastore.create_user(**user_data)
db.session.commit()
return redirect(url_for('login'))
except Exception as e:
print(e)
return render_template('register.html')
2. Erreur : AttributeError: 'User' object has no attribute 'password_hash'
Code incorrect :
@app.route('/register', methods=['GET', 'POST'])
@security.auth_required('anonymous')
def register():
if request.method == 'POST':
user_data = {
'email': request.form['email'],
'password': request.form['password']
}
try:
user_datastore.create_user(**user_data)
db.session.commit()
return redirect(url_for('login'))
except Exception as e:
print(e)
return render_template('register.html')
Code correct :
@app.route('/register', methods=['GET', 'POST'])
@security.auth_required('anonymous')
def register():
if request.method == 'POST':
user_data = {
'email': request.form['email'],
'password': request.form['password']
}
try:
user_datastore.create_user(**user_data)
db.session.commit()
return redirect(url_for('login'))
except Exception as e:
print(e)
return render_template('register.html')
3. Erreur : TypeError: create_user() missing 1 required positional argument: 'email'
Code incorrect :
@app.route('/register', methods=['GET', 'POST'])
@security.auth_required('anonymous')
def register():
if request.method == 'POST':
user_data = {
'email': request.form['email'],
'password': request.form['password']
}
try:
user_datastore.create_user(**user_data)
db.session.commit()
return redirect(url_for('login'))
except Exception as e:
print(e)
return render_template('register.html')
Code correct :
@app.route('/register', methods=['GET', 'POST'])
@security.auth_required('anonymous')
def register():
if request.method == 'POST':
user_data = {
'email': request.form['email'],
'password': request.form['password']
}
try:
user_datastore.create_user(**user_data)
db.session.commit()
return redirect(url_for('login'))
except Exception as e:
print(e)
return render_template('register.html')
Pour aller plus loin
- Gestion des rôles et permissions : Ajoutez différents rôles (admin, user) avec différentes permissions.
- Intégration d'OAuth : Permettez à vos utilisateurs de s'authentifier avec leurs comptes Google, Facebook, etc.
- Email confirmation : Demandez une confirmation email lors de l'enregistrement pour vérifier que l'email est bien le sien.
Défi pratique
Créez un petit script CLI qui permet d'ajouter un utilisateur et de lui attribuer un rôle. Utilisez flask-script ou click.
## cli.py
from flask import Flask, render_template, redirect, url_for, request
from models import db, User, Role, user_datastore
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///tasks.db'
app.config['SECRET_KEY'] = 'your_secret_key'
app.config['SECURITY_PASSWORD_SALT'] = 'your_secret_salt'
db.init_app(app)
@app.route('/add_user', methods=['GET', 'POST'])
def add_user():
if request.method == 'POST':
email = request.form['email']
password = request.form['password']
role_name = request.form['role']
user_datastore.create_user(email=email, password=password)
role = Role.query.filter_by(name=role_name).first()
if role:
user_datastore.add_role_to_user(user_datastore.find_user(email=email), role)
db.session.commit()
return redirect(url_for('index'))
return render_template('add_user.html')
if __name__ == '__main__':
app.run(debug=True)
Ajoutez un template templates/add_user.html pour le formulaire.
<!-- templates/add_user.html -->
{% extends 'base.html' %}
{% block content %}
<h2>Add User</h2>
<form method="post">
<input type="email" name="email" placeholder="Email" required>
<input type="password" name="password" placeholder="Password" required>
<select name="role">
{% for role in roles %}
<option value="role.name">role.name</option>
{% endfor %}
</select>
<button type="submit">Add User</button>
</form>
{% endblock %}
Ajoutez une route pour afficher la liste des utilisateurs et leurs rôles.
@app.route('/users')
@login_required
def users():
users = user_datastore.find_users()
return render_template('users.html', users=users)
Créez un template templates/users.html.
<!-- templates/users.html -->
{% extends 'base.html' %}
{% block content %}
<h2>Users</h2>
<table>
<tr>
<th>Email</th>
<th>Roles</th>
</tr>
{% for user in users %}
<tr>
<td>user.email</td>
<td>', '.join(role.name for role in user.roles)</td>
</tr>
{% endfor %}
</table>
{% endblock %}
Ensuite, vous pouvez exécuter le script CLI avec :
python cli.py add_user
Et ajouter des utilisateurs en spécifiant leur email, mot de passe et rôle.
Ce tutoriel vous a permis d'acquérir une compréhension approfondie de l'authentification dans Flask. Vous avez appris à configurer l'environnement, à créer des modèles de données, à gérer les sessions et à protéger certaines routes. N'hésitez pas à explorer davantage pour approfondir vos connaissances en sécurité Web avec Flask.