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.

Exemple d'un jeton JWT codé à gauche et de la version décodée du jeton montrant les composants individuels à droite.

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.

  1. 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é.
  2. 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

  1. 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})
  2. 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.

  3. 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 decorated

    Cette 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.

  4. 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&gt ; 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.