L’authentification défaillante reste une vulnérabilité persistante dans les applications web modernes – elle figure toujours en bonne place dans le top 10 des risques de sécurité des API de l’OWASP.
Les effets de cette vulnérabilité peuvent être graves. Elles peuvent permettre un accès non autorisé à des données sensibles et compromettre l’intégrité du système. Pour garantir un accès sécurisé aux applications et à leurs ressources, il est essentiel d’utiliser des mécanismes d’authentification robustes.
Découvrez comment vous pouvez mettre en œuvre l’authentification des utilisateurs dans Flask à l’aide de jetons Web JSON (JWT), une méthode populaire et efficace basée sur les jetons.
Authentification par jeton à l’aide de jetons Web JSON
L’authentification par jeton utilise une chaîne de caractères cryptée pour valider et autoriser l’accès à un système ou à une ressource. Vous pouvez mettre en œuvre ce type d’authentification à l’aide de différentes méthodes, notamment les jetons de session, les clés API et les jetons Web JSON.
Les JWT, en particulier, offrent une approche sécurisée et compacte pour la transmission des informations d’identification des utilisateurs entre les applications côté client et les serveurs.
Un JWT se compose de trois éléments principaux : l’en-tête, la charge utile et la signature. L’en-tête contient des métadonnées sur le jeton, y compris l’algorithme de hachage utilisé pour coder le jeton.
La charge utile contient les informations d’identification de l’utilisateur, telles que l’ID de l’utilisateur et les autorisations. Enfin, la signature garantit la validité du jeton en vérifiant son contenu à l’aide d’une clé secrète.
Les JWT permettent d’authentifier les utilisateurs et de stocker les données de session dans le jeton lui-même.
La mise en place d’un projet Flask et d’une base de données MongoDB
Pour commencer, créez un nouveau répertoire de projet à l’aide d’un terminal :
mkdir flask-project
cd flask-project
Ensuite, installez virtualenvpour créer un environnement de développement virtuel local pour votre projet Flask.
virtualenv venv
Enfin, activez l’environnement virtuel.
# Unix or MacOS:
source venv/bin/activate
# Windows:
.\venv\Scripts\activate
Vous pouvez trouver le code de ce projet dans ce dépôt GitHub.
Installer les paquets nécessaires
Dans le répertoire racine du dossier de votre projet, créez un nouveau fichier requirements.txt et ajoutez ces dépendances au projet :
flask
pyjwt
python-dotenv
pymongo
bcrypt
Enfin, exécutez la commande ci-dessous pour installer les paquets. Assurez-vous d’avoir pip (gestionnaire de paquets) ; si ce n’est pas le cas, installez-le sur votre système Windows, Mac ou Linux.
pip install -r requirements.txt
Créer une base de données MongoDB
Créez une base de données MongoDB. Vous pouvez configurer une base de données MongoDB locale ou créer un cluster sur MongoDB Atlas, un service MongoDB basé sur le cloud.
Une fois la base de données créée, copiez l’URI de connexion, créez un fichier .env dans le répertoire racine de votre projet, et ajoutez-le comme suit :
MONGO_URI="<insert-connection-uri>"
Enfin, configurez la connexion à la base de données à partir de votre application Flask. Créez un nouveau fichier utils/db.py dans le répertoire racine de votre projet, avec ce code :
from pymongo import MongoClient
def connect_to_mongodb(mongo_uri):
client = MongoClient(mongo_uri)
db = client.get_database("users")
return db
Cette fonction établit une connexion à la base de données MongoDB à l’aide de l’URI de connexion fourni. Elle crée ensuite un nouveau fichier utilisateurs si elle n’existe pas, et renvoie l’instance de base de données correspondante.
Créer le serveur Web Flask
Une fois la base de données configurée, allez-y et créez un fichier app.py dans le répertoire racine du dossier du projet, et ajoutez le code suivant pour créer une instance de l’application Flask.
from flask import Flask
from routes.user_auth import register_routes
from utils.db import connect_to_mongodb
import os
from dotenv import load_dotenv
app = Flask(__name__)
load_dotenv()
mongo_uri = os.getenv('MONGO_URI')
db = connect_to_mongodb(mongo_uri)
register_routes(app, db)
if __name__ == '__main__':
app.run(debug=True)
Créer les points d’extrémité de l’API d’authentification
Pour mettre en œuvre l’authentification des utilisateurs dans votre application Flask, il est essentiel de définir les points d’extrémité de l’API nécessaires qui gèrent les opérations liées à l’authentification.
Cependant, il faut d’abord définir le modèle pour les données des utilisateurs. Pour ce faire, créez un nouveau model/user_model.py dans le répertoire racine, et ajoutez le code suivant.
from pymongo.collection import Collection
from bson.objectid import ObjectId
class User:
def __init__(self, collection: Collection, username: str, password: str):
self.collection = collection
self.username = username
self.password = password
def save(self):
user_data = {
'username': self.username,
'password': self.password
}
result = self.collection.insert_one(user_data)
return str(result.inserted_id)
@staticmethod
def find_by_id(collection: Collection, user_id: str):
return collection.find_one({'_id': ObjectId(user_id)})
@staticmethod
def find_by_username(collection: Collection, username: str):
return collection.find_one({'username': username})
Le code ci-dessus spécifie un Utilisateur qui sert de modèle de données et définit plusieurs méthodes pour interagir avec une collection MongoDB afin d’effectuer des opérations liées à l’utilisateur.
- La classe sauver enregistre un nouveau document utilisateur avec le nom d’utilisateur et le mot de passe fournis dans la collection MongoDB et renvoie l’ID du document inséré.
- La méthode find_by_id et find_by_username permettent de récupérer des documents d’utilisateur dans la collection en fonction de l’identifiant ou du nom d’utilisateur fourni, respectivement.
Définir les routes d’authentification
- Commençons par définir la route d’enregistrement. Cette route ajoutera de nouvelles données d’utilisateur à la collection d’utilisateurs de MongoDB. Dans le répertoire racine, créez une nouvelle route routes/user_auth.py et le code suivant.
import jwt
from functools import wraps
from flask import jsonify, request, make_response
from models.user_model import User
import bcrypt
import os
def register_routes(app, db):
collection = db.users
app.config['SECRET_KEY'] = os.urandom(24)
@app.route('/api/register', methods=['POST'])
def register():
username = request.json.get('username')
password = request.json.get('password')
existing_user = User.find_by_username(collection, username)
if existing_user:
return jsonify({'message': 'Username already exists!'})
hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())
new_user = User(collection, username, hashed_password.decode('utf-8'))
user_id = new_user.save()
return jsonify({'message': 'User registered successfully!', 'user_id': user_id}) - Mettre en œuvre la fonctionnalité de connexion, pour gérer le processus d’authentification et vérifier les informations d’identification de l’utilisateur. Sous la route d’enregistrement, ajoutez le code suivant.
@app.route('/api/login', methods=['POST'])
def login():
username = request.json.get('username')
password = request.json.get('password')
user = User.find_by_username(collection, username)
if user:
if bcrypt.checkpw(password.encode('utf-8'), user['password'].encode('utf-8')):
token = jwt.encode({'user_id': str(user['_id'])}, app.config['SECRET_KEY'], algorithm='HS256')
response = make_response(jsonify({'message': 'Login successful!'}))
response.set_cookie('token', token)
return response
return jsonify({'message': 'Invalid username or password'})Le point de terminaison de connexion fait deux choses : il vérifie les informations d’identification de l’utilisateur et, en cas d’authentification réussie, il génère un JWT unique pour cet utilisateur. Il définit ce jeton en tant que cookie dans la réponse, avec une charge utile JSON indiquant que la connexion a réussi. Si les informations d’identification ne sont pas valides, il renvoie une réponse JSON pour l’indiquer.
- Définir une fonction décoratrice qui vérifie les jetons Web JSON (JWT) transmis avec les demandes d’API ultérieures. Ajoutez le code ci-dessous à l’intérieur de la fonction register_routes bloc de code de fonction.
def token_required(f):
@wraps(f)
def decorated(*args, **kwargs):
token = request.cookies.get('token')
if not token:
return jsonify({'message': 'Token is missing!'}), 401
try:
data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
current_user = User.find_by_id(collection, data['user_id'])
except jwt.ExpiredSignatureError:
return jsonify({'message': 'Token has expired!'}), 401
except jwt.InvalidTokenError:
return jsonify({'message': 'Invalid token!'}), 401
return f(current_user, *args, **kwargs)
return decoratedCette fonction décorative garantit la présence d’un jeton JWT valide dans les demandes d’API ultérieures. Elle vérifie si le jeton est manquant, expiré ou valide, et renvoie une réponse JSON appropriée si c’est le cas.
- Enfin, créez une route protégée.
@app.route('/api/users', methods=['GET'])
@token_required
def get_users(current_user):
users = list(collection.find({}, {'_id': 0}))
return jsonify(users)
Ce point d’accès gère la logique de récupération des données de l’utilisateur dans la base de données, mais il exige que le client qui envoie des requêtes inclue un jeton valide pour accéder aux données.
Enfin, exécutez la commande ci-dessous pour démarrer le serveur de développement.
flask run
Pour tester l’enregistrement, la connexion et le point de terminaison des utilisateurs protégés, vous pouvez utiliser Postman ou tout autre client API. Envoyez des requêtes à http://localhost:5000/api/<route> ; et observez les réponses pour vérifier la fonctionnalité de ces points d’extrémité de l’API.
L’authentification par jeton est-elle une mesure de sécurité infaillible ?
Les jetons Web JSON constituent un moyen robuste et efficace d’authentifier les utilisateurs de votre application Web. Cependant, il est important de comprendre que l’authentification par jeton n’est pas infaillible ; elle n’est qu’une pièce d’un puzzle de sécurité plus large.
Combinez l’authentification par jeton avec d’autres bonnes pratiques de sécurité. N’oubliez pas de surveiller en permanence et d’adopter des pratiques de sécurité cohérentes ; vous améliorerez considérablement la sécurité globale de vos applications Flask.