REST et JWT - Partie 1 : Concepts et Théorie

← Retour aux tutoriels | Partie 2 : REST/JWT en PHP → | Partie 3 : REST/JWT en Python →

Parcours d'apprentissage (novice vers expert)

Objectif: comprendre les fondations REST/JWT avant de coder une API sécurisée.

Fonctionnement: valide chaque concept avec un cas concret, puis relie-le aux parties implémentation PHP/Python.

1. Qu'est-ce qu'une API REST ?

REST (Representational State Transfer)

REST est un style d'architecture pour les systèmes distribués, défini par Roy Fielding en 2000. Une API REST utilise HTTP de manière standardisée pour exposer des ressources.

Principes fondamentaux de REST

1. Architecture Client-Serveur

2. Sans état (Stateless)

3. Cacheable

4. Interface uniforme

5. Système en couches (Layered)

2. Méthodes HTTP (Verbes)

Méthode Description Idempotent Safe Exemple
GET Récupérer une ressource GET /users/123
POST Créer une ressource POST /users
PUT Remplacer complètement une ressource PUT /users/123
PATCH Modifier partiellement une ressource PATCH /users/123
DELETE Supprimer une ressource DELETE /users/123
HEAD Comme GET sans le body HEAD /users/123
OPTIONS Obtenir les méthodes supportées OPTIONS /users

Définitions

3. Codes de statut HTTP

2xx : Succès

200 OK              → Requête réussie (GET, PUT, PATCH)
201 Created         → Ressource créée (POST)
204 No Content      → Succès sans contenu (DELETE)

3xx : Redirection

301 Moved Permanently  → Ressource déplacée définitivement
302 Found              → Redirection temporaire
304 Not Modified       → Cache valide (utiliser la version locale)

4xx : Erreur client

400 Bad Request           → Syntaxe invalide
401 Unauthorized          → Authentification requise
403 Forbidden             → Authentifié mais pas autorisé
404 Not Found             → Ressource inexistante
405 Method Not Allowed    → Méthode HTTP non supportée
409 Conflict              → Conflit (ex: email déjà utilisé)
422 Unprocessable Entity  → Validation échouée
429 Too Many Requests     → Rate limiting dépassé

5xx : Erreur serveur

500 Internal Server Error  → Erreur générale du serveur
502 Bad Gateway            → Proxy/Gateway invalide
503 Service Unavailable    → Serveur surchargé ou maintenance
504 Gateway Timeout        → Timeout du proxy/gateway

4. Structure d'une API REST

Convention de nommage des endpoints

# CRUD sur les utilisateurs
GET     /api/users           → Liste tous les utilisateurs
GET     /api/users/123       → Récupère l'utilisateur #123
POST    /api/users           → Crée un nouvel utilisateur
PUT     /api/users/123       → Remplace l'utilisateur #123
PATCH   /api/users/123       → Modifie l'utilisateur #123
DELETE  /api/users/123       → Supprime l'utilisateur #123

# Ressources imbriquées
GET     /api/users/123/posts          → Posts de l'utilisateur #123
POST    /api/users/123/posts          → Créer un post pour l'utilisateur #123
GET     /api/users/123/posts/456      → Post #456 de l'utilisateur #123
DELETE  /api/users/123/posts/456      → Supprimer le post #456

# Actions spécifiques (éviter si possible)
POST    /api/users/123/activate       → Activer l'utilisateur
POST    /api/users/123/reset-password → Réinitialiser le mot de passe

Bonnes pratiques de nommage

5. Format des données : JSON

Requête POST : Créer un utilisateur

POST /api/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

{
  "username": "john_doe",
  "email": "john@example.com",
  "password": "SecurePass123!",
  "profile": {
    "firstName": "John",
    "lastName": "Doe",
    "age": 30
  }
}

Réponse 201 Created

HTTP/1.1 201 Created
Content-Type: application/json
Location: /api/users/123

{
  "id": 123,
  "username": "john_doe",
  "email": "john@example.com",
  "profile": {
    "firstName": "John",
    "lastName": "Doe",
    "age": 30
  },
  "createdAt": "2025-11-30T10:30:00Z",
  "updatedAt": "2025-11-30T10:30:00Z",
  "_links": {
    "self": "/api/users/123",
    "posts": "/api/users/123/posts"
  }
}

Réponse d'erreur 400 Bad Request

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Les données fournies sont invalides",
    "details": [
      {
        "field": "email",
        "message": "Format d'email invalide"
      },
      {
        "field": "password",
        "message": "Le mot de passe doit contenir au moins 8 caractères"
      }
    ]
  }
}

Pagination

GET /api/users?page=2&limit=20

{
  "data": [
    { "id": 21, "username": "user21" },
    { "id": 22, "username": "user22" }
    // ... 20 résultats
  ],
  "pagination": {
    "page": 2,
    "limit": 20,
    "total": 150,
    "totalPages": 8,
    "hasNext": true,
    "hasPrev": true
  },
  "_links": {
    "first": "/api/users?page=1&limit=20",
    "prev": "/api/users?page=1&limit=20",
    "self": "/api/users?page=2&limit=20",
    "next": "/api/users?page=3&limit=20",
    "last": "/api/users?page=8&limit=20"
  }
}

6. Qu'est-ce que JWT (JSON Web Token) ?

Définition

JWT est un standard ouvert (RFC 7519) pour créer des tokens d'accès sécurisés. C'est une chaîne encodée contenant des informations (claims) signées cryptographiquement.

Structure d'un JWT

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

│─────────── Header ──────────│─────────────── Payload ────────────────│────────── Signature ─────────│

1. Header (en-tête)

{
  "alg": "HS256",      // Algorithme de signature (HMAC SHA-256)
  "typ": "JWT"         // Type de token
}

2. Payload (données)

{
  // Claims standards (RFC 7519)
  "iss": "https://api.example.com",  // Issuer (émetteur)
  "sub": "123",                       // Subject (utilisateur)
  "aud": "https://app.example.com",   // Audience (destinataire)
  "exp": 1735560000,                  // Expiration (timestamp)
  "nbf": 1735559000,                  // Not Before (pas avant)
  "iat": 1735559000,                  // Issued At (émis à)
  "jti": "abc123",                    // JWT ID (identifiant unique)
  
  // Claims personnalisés
  "username": "john_doe",
  "email": "john@example.com",
  "roles": ["user", "admin"]
}

3. Signature

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret_key
)

La signature garantit que :

7. Pourquoi utiliser JWT ?

Avantages

Inconvénients

8. Flux d'authentification JWT

Schéma complet

┌─────────┐                                          ┌─────────┐
│ Client  │                                          │ Serveur │
└────┬────┘                                          └────┬────┘
     │                                                    │
     │  1. POST /api/auth/login                          │
     │     { username, password }                        │
     ├──────────────────────────────────────────────────►│
     │                                                    │
     │                           2. Vérifier credentials │
     │                              Générer JWT          │
     │                                                    │
     │  3. 200 OK                                        │
     │     { token: "eyJ...", refreshToken: "..." }      │
     │◄──────────────────────────────────────────────────┤
     │                                                    │
     │  Stocker le token (localStorage / cookie)         │
     │                                                    │
     │  4. GET /api/users/me                             │
     │     Authorization: Bearer eyJ...                  │
     ├──────────────────────────────────────────────────►│
     │                                                    │
     │                           5. Vérifier signature   │
     │                              Décoder payload      │
     │                              Vérifier expiration  │
     │                                                    │
     │  6. 200 OK                                        │
     │     { id, username, email, ... }                  │
     │◄──────────────────────────────────────────────────┤
     │                                                    │
     │  ... Le token expire après 15 minutes ...         │
     │                                                    │
     │  7. GET /api/users/me                             │
     │     Authorization: Bearer eyJ... (expiré)         │
     ├──────────────────────────────────────────────────►│
     │                                                    │
     │  8. 401 Unauthorized                              │
     │     { error: "Token expired" }                    │
     │◄──────────────────────────────────────────────────┤
     │                                                    │
     │  9. POST /api/auth/refresh                        │
     │     { refreshToken: "..." }                       │
     ├──────────────────────────────────────────────────►│
     │                                                    │
     │                           10. Vérifier refresh    │
     │                               Générer nouveau JWT │
     │                                                    │
     │  11. 200 OK                                       │
     │      { token: "eyJ... (nouveau)" }                │
     │◄──────────────────────────────────────────────────┤
     │                                                    │

9. Access Token vs Refresh Token

Aspect Access Token Refresh Token
Durée de vie Courte (15 min - 1h) Longue (7 jours - 30 jours)
Usage Accéder aux ressources protégées Obtenir un nouveau access token
Envoi À chaque requête (header Authorization) Uniquement pour le refresh
Stockage Memory / sessionStorage (SPA) httpOnly cookie (sécurisé)
Révocation Difficile (attendre expiration) Possible (stocké en DB)
Risque XSS Élevé si dans localStorage Faible si httpOnly cookie

Stratégie recommandée

10. Sécurité JWT : Bonnes pratiques

1. Choix de l'algorithme

✅ RECOMMANDÉ
HS256 (HMAC SHA-256)    → Secret symétrique, simple
RS256 (RSA SHA-256)     → Clés asymétriques, plus sécurisé pour microservices
ES256 (ECDSA SHA-256)   → Clés asymétriques, signatures plus courtes

❌ À ÉVITER
none                    → Pas de signature (vulnérable !)
HS512 avec clé faible   → Force brute possible

2. Stockage sécurisé

❌ NE PAS FAIRE
localStorage            → Vulnérable aux attaques XSS
Cookie sans httpOnly    → Accessible en JavaScript

✅ RECOMMANDÉ
Memory (variable JS)    → Perdu au refresh mais sécurisé
sessionStorage          → Compromis acceptable pour SPA
httpOnly Cookie         → Le plus sécurisé (pas accessible en JS)

3. Secret fort

# Générer un secret robuste (256 bits minimum)
openssl rand -base64 32

# Exemple de secret fort
JWT_SECRET=3F2504E0-4F89-11D3-9A0C-0305E82C3301-8A8F6B9C2D3E4F5A6B7C8D9E0F1A2B3C

4. Validation stricte

À vérifier côté serveur :
✓ Signature valide (avec la bonne clé)
✓ Token non expiré (exp)
✓ Token pas encore valide (nbf)
✓ Issuer correct (iss)
✓ Audience correcte (aud)
✓ Token ID pas en blacklist (jti)

5. HTTPS obligatoire

✅ TOUJOURS utiliser HTTPS en production
❌ JAMAIS envoyer un JWT en HTTP (interception possible)

6. Ne pas stocker de données sensibles

❌ NE PAS mettre dans le JWT
{
  "password": "...",           // JAMAIS de mot de passe
  "creditCard": "...",         // JAMAIS de données bancaires
  "ssn": "..."                 // JAMAIS de données personnelles sensibles
}

✅ Stocker uniquement
{
  "userId": 123,
  "username": "john_doe",
  "roles": ["user"],
  "email": "john@example.com"  // OK car pas ultra-sensible
}

7. Courte durée de vie

# Access token : 15 minutes à 1 heure maximum
exp: Date.now() + 15 * 60 * 1000

# Refresh token : 7 à 30 jours
exp: Date.now() + 7 * 24 * 60 * 60 * 1000

8. Protection CSRF

Si JWT dans cookie :
→ Utiliser SameSite=Strict ou Lax
→ Vérifier l'origine de la requête
→ Token CSRF complémentaire pour les mutations

11. Exemple d'API REST avec authentification JWT

Endpoints d'authentification

POST   /api/auth/register      → Créer un compte
POST   /api/auth/login         → Se connecter (obtenir tokens)
POST   /api/auth/refresh       → Renouveler l'access token
POST   /api/auth/logout        → Se déconnecter (révoquer refresh token)
GET    /api/auth/me            → Informations de l'utilisateur connecté

Endpoints protégés (nécessitent JWT)

GET    /api/users              → Liste des utilisateurs (admin)
GET    /api/users/:id          → Détails d'un utilisateur
PUT    /api/users/:id          → Modifier un utilisateur (owner ou admin)
DELETE /api/users/:id          → Supprimer un utilisateur (admin)

GET    /api/posts              → Liste des posts publics
POST   /api/posts              → Créer un post (authentifié)
GET    /api/posts/:id          → Détails d'un post
PUT    /api/posts/:id          → Modifier un post (owner)
DELETE /api/posts/:id          → Supprimer un post (owner ou admin)

Exemple de requête authentifiée

GET /api/users/me HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJ1c2VybmFtZSI6ImpvaG5fZG9lIiwicm9sZXMiOlsidXNlciJdLCJpYXQiOjE3MzU1NTkwMDAsImV4cCI6MTczNTU1OTkwMH0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Accept: application/json

Réponse

HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": 123,
  "username": "john_doe",
  "email": "john@example.com",
  "profile": {
    "firstName": "John",
    "lastName": "Doe",
    "avatar": "https://cdn.example.com/avatars/123.jpg"
  },
  "roles": ["user"],
  "createdAt": "2025-01-15T10:30:00Z"
}

12. Outils de test

Décoder un JWT

Site web : jwt.io

Permet de décoder et vérifier un JWT manuellement.

Tester une API REST

Exemple avec curl

# Login
curl -X POST https://api.example.com/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"john_doe","password":"secret123"}'

# Utiliser le token
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

curl -X GET https://api.example.com/api/users/me \
  -H "Authorization: Bearer $TOKEN"

Exemple avec HTTPie

# Login
http POST https://api.example.com/api/auth/login \
  username=john_doe password=secret123

# Utiliser le token
http GET https://api.example.com/api/users/me \
  "Authorization: Bearer $TOKEN"

Défi progression (vers expert)

Mission: modéliser une stratégie complète de sécurisation JWT pour une API réelle.

← Retour aux tutoriels | Partie 2 : REST/JWT en PHP → | Partie 3 : REST/JWT en Python →