Aller au contenu

Développement d'API Rest - Partie 2 : JWT et Connexion

Mise à jour :

Dans un monde où les API sont au cœur des architectures modernes, garantir leur sécurité est une priorité absolue. Dans notre précédent article, nous avons exploré les bases de Connexion, un puissant framework qui permet de construire des API RESTful en Python à partir de spécifications OpenAPI. Vous avez découvert comment démarrer rapidement, définir vos endpoints, et structurer votre projet pour répondre aux besoins des utilisateurs.

Mais qu’en est-il de la sécurisation de ces endpoints ? C’est ici que JWT (JSON Web Tokens) entre en jeu. Avant de plonger dans l’implémentation dans notre API Connexion, prenons un moment pour comprendre JWT (JSON Web Tokens) et pourquoi il est si largement utilisé pour sécuriser les APIs.

Qu’est-ce qu’un JWT ?

Un JWT est un standard ouvert (RFC 7519) qui permet d’échanger des informations entre deux parties de manière sécurisée. Ces informations sont encapsulées dans un token compact et auto-contenu. Le JWT est composé de trois parties distinctes :

  1. Header : Contient le type de token (JWT) et l’algorithme de signature utilisé (par exemple, HMAC-SHA256).
  2. Payload : Contient les données que vous souhaitez transmettre (appelées “claims”), comme l’identifiant de l’utilisateur et son rôle.
  3. Signature : Garantit que le token n’a pas été modifié en utilisant une clé secrète.

Un exemple de JWT encodé peut ressembler à ceci :

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwicm9sZSI6ImFkbWluIiwiZXhwIjoxNjkwODg2NjAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Lorsque le serveur génère un JWT, il est signé avec une clé secrète connue uniquement par le serveur. Le client inclut ce token dans chaque requête pour prouver son identité.

Fonctionnement Général de JWT dans une API

Voici un aperçu des étapes principales lorsque vous utilisez JWT dans une API :

  1. Authentification initiale :

    • L’utilisateur envoie ses identifiants (par exemple, email et mot de passe) à l’endpoint /login.
    • Si les identifiants sont valides, le serveur génère un JWT et le renvoie au client.
  2. Requêtes sécurisées :

    • Pour chaque requête vers un endpoint protégé, le client inclut le JWT dans l’en-tête Authorization :

      Authorization: Bearer <token>
    • Le serveur décode le token, vérifie sa validité (signature, expiration, etc.), et autorise ou refuse l’accès.

  3. Expiration et renouvellement :

    • Une fois le JWT expiré, l’utilisateur doit se reconnecter pour obtenir un nouveau token ou utiliser un système de “refresh token”.

Pourquoi utiliser JWT dans une API ?

L’authentification avec JWT présente plusieurs avantages par rapport aux méthodes traditionnelles, comme les sessions basées sur des cookies :

  1. Sans état (Stateless) :

    • Le serveur n’a pas besoin de stocker les sessions en mémoire ou dans une base de données.
    • Tout ce dont le serveur a besoin est la clé secrète pour valider le JWT.
  2. Autonomie des données :

    • Les informations sur l’utilisateur (comme son rôle ou son identifiant) sont directement intégrées dans le JWT.
    • Cela simplifie les interactions serveur-client, car tout est inclus dans le token.
  3. Sûreté :

    • Chaque token est signé numériquement, garantissant que son contenu n’a pas été altéré.
    • Une date d’expiration peut être incluse pour limiter la durée de validité d’un token.
  4. Compatibilité multiplateforme :

    • Les JWT sont indépendants des technologies backend ou frontend. Ils peuvent être utilisés avec n’importe quelle API REST ou GraphQL.

Quand éviter JWT ?

Bien que puissant, JWT n’est pas toujours la solution idéale. Voici quelques situations où il est préférable d’utiliser une méthode différente :

  • Si vous avez besoin de révoquer les tokens immédiatement après la déconnexion (JWT est difficile à invalider sans base de données).
  • Pour des systèmes où la gestion des sessions côté serveur est simple et suffisante.
  • Lorsque vous traitez des données extrêmement sensibles qui ne doivent pas transiter dans les requêtes.

Mise en place de JWT dans une API Connexion

Maintenant que nous avons vu pourquoi JWT est utile pour sécuriser une API, passons à sa mise en œuvre pratique avec Connexion. Dans cette section, nous allons configurer notre projet pour générer, valider et utiliser des tokens JWT dans une API existante.

Gestion des Tokens JWT

Ajoutons un module pour gérer la génération et la validation des tokens JWT.

Le fichier app/utils/jwt.py** :

import jwt
from datetime import datetime, timedelta
SECRET_KEY = "votre-secret-key-qu-il-faut-absolument-changer" # Remplacez par une clé sécurisée
def create_token(email, role):
"""
Génère un token JWT pour un utilisateur.
"""
payload = {
"sub": email, # Identifiant de l'utilisateur (doit être une chaîne)
"role": role, # Rôle de l'utilisateur (admin, teacher, student)
"exp": datetime.utcnow() + timedelta(hours=1), # Expiration (1h)
"iat": datetime.utcnow() # Date de création
}
return jwt.encode(payload, SECRET_KEY, algorithm="HS256")
def decode_token(token):
"""
Décode et valide un token JWT.
"""
try:
return jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
except jwt.ExpiredSignatureError:
raise Exception("Token expired")
except jwt.InvalidTokenError:
raise Exception("Invalid token")

Ajouter l’Endpoint /login**

L’endpoint /login permet à un utilisateur de s’authentifier avec ses identifiants. Si les informations sont valides, un token JWT est généré et retourné.

paths:
/login:
post:
summary: Authenticate a user and return a JWT token
operationId: app.routes.user.login
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
email:
type: string
format: email
password:
type: string
required:
- email
- password
responses:
200:
description: Token generated successfully
content:
application/json:
schema:
type: object
properties:
access_token:
type: string
token_type:
type: string
401:
description: Invalid credentials

Implémentation de l’Endpoint /login

Voyons maintenant comment implémenter l’endpoint /login dans notre application. Nous allons vérifier les identifiants de l’utilisateur, générer un token JWT et le renvoyer en cas de succès.

from sqlalchemy.future import select
from app.models.base import SessionLocal
from app.models.user import User
from app.utils.jwt_utils import create_token
import bcrypt
async def login(body):
"""
Endpoint pour authentifier un utilisateur.
"""
email = body.get("email")
password = body.get("password")
async with SessionLocal() as session:
# Rechercher l'utilisateur par email
result = await session.execute(select(User).where(User.email == email))
user = result.scalars().first()
if not user or not bcrypt.checkpw(password.encode("utf-8"), user.password.encode("utf-8")):
return {"msg": "Invalid credentials"}, 401
# Générer un token JWT pour l'utilisateur
token = create_token(user.email, user.role)
return {"access_token": token}

Ajouter un Schéma de Sécurité dans api.yaml**

Dans le fichier openapi.yaml, configurez un schéma de sécurité bearerAuth pour les endpoints nécessitant un token JWT.

components:
securitySchemes:
BearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
x-bearerInfoFunc: app.utils.jwt.verify_bearer_token

Il ne faut pas oublier de définir la fonction verify_bearer_token dans la clé x-bearerInfoFunc.

Protéger les Endpoints avec JWT

Pour protéger un endpoint, ajoutez le schéma de sécurité bearerAuth dans la section security de l’endpoint.

paths:
/users:
get:
summary: Get all users
operationId: app.routes.user.get_users
security:
- BearerAuth: []
responses:
200:
description: List of users
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/User"

Tester l’API**

  1. Obtenir un Token JWT avec /login :

    Terminal window
    curl -X POST http://localhost:8000/login \
    -H "Content-Type: application/json" \
    -d '{
    "email": "alice@example.com",
    "password": "securepass"
    }'

    Réponse attendue :

    {
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
    }
  2. Accéder à un Endpoint Protégé :

    Terminal window
    curl -X GET http://localhost:8000/users \
    -H "Authorization: Bearer <votre_token>"

Conclusion

Avec ces étapes, vous avez une API sécurisée avec JWT fonctionnant sous Connexion. Vous avez appris à générer des tokens JWT, à les valider et à les utiliser pour protéger vos endpoints. JWT est un outil puissant pour sécuriser les API, en particulier dans un environnement sans état comme les APIs RESTful.

Dans le prochain guide, nous verrons comment gérer les rôles et les permissions pour un contrôle d’accès plus fin.