API — Authentification¶
Cette page documente les endpoints gérés par AuthController et UserController, tous deux montés sous le préfixe /api/auth.
Contrôleurs couverts
AuthController→POST /login,POST /logoutUserController→GET /status,GET /me,PUT /me
Les endpoints /register et /me DELETE n'existent pas dans le code : la création de compte utilisateur final passe par POST /api/admin/users (côté formateur) ou par le parcours frontend qui appelle la couche de service. Voir admin.md.
Vue d'ensemble¶
| Méthode | Endpoint | Auth | Description |
|---|---|---|---|
POST |
/api/auth/login |
Aucune | Connexion utilisateur, génère un cookie de session et un securityToken |
POST |
/api/auth/logout |
Session | Déconnexion (comportement altéré en Chaos Métier niveau 3+) |
GET |
/api/auth/status |
Aucune | État de la session courante (authentifié, token, id) |
GET |
/api/auth/me |
Session | Profil complet de l'utilisateur connecté |
PUT |
/api/auth/me |
Session | Mise à jour du profil avec validation stricte |
POST /api/auth/login¶
Authentifie un utilisateur final via email + mot de passe. Crée une session HTTP (cookie JSESSIONID) et génère un securityToken UUID utilisé ensuite par le parcours checkout.
Auth : aucune
Contrôleur : AuthController.login()
Service appelé : AuthService.login() (validation BCrypt contre la table users)
Requête¶
POST /api/auth/login HTTP/1.1
Content-Type: application/json
{
"email": "alice@example.com",
"password": "alice123"
}
| Champ | Type | Requis | Contrainte |
|---|---|---|---|
email |
string | oui | Format email valide (@Email) |
password |
string | oui | Non vide (@NotBlank) |
Le DTO de liaison est com.perfshop.dto.LoginRequest.
Réponse — 200 OK¶
{
"success": true,
"securityToken": "a7f1c2e8-4b9d-4f11-9c8e-1a2b3c4d5e6f",
"id": 42,
"email": "alice@example.com",
"firstName": "Alice",
"lastName": "Durand"
}
| Champ | Description |
|---|---|
securityToken |
UUID requis pour POST /api/orders (validation finale de commande) |
id |
Identifiant interne de l'utilisateur |
email, firstName, lastName |
Infos de base pour pré-remplir le profil côté frontend |
Headers de réponse additionnels¶
En fonction des chaos actifs, le login peut enrichir les headers HTTP de la réponse :
| Header | Activation | Chaos |
|---|---|---|
X-Session-Token, X-Request-ID, etc. |
Scripting ≥ 1 | Chaos Scripting |
X-Debug-Token |
Sécurité ≥ 3 | S7 — Token HMAC faible |
S7 — Token HMAC faible
En Chaos Sécurité niveau 3 (Expert) et au-delà, le header X-Debug-Token est ajouté à la réponse. Il contient un token HMAC-SHA256 signé avec la clé secret123 — connue de l'étudiant. L'étudiant peut décoder le token (format base64(userId:timestamp).hmac-sha256), modifier le userId, re-signer avec la clé et usurper une identité dans les endpoints qui lisent ce header.
Timing attack pédagogique (S6)¶
En Chaos Sécurité niveau 2 (Confirmé) et au-delà, le temps de réponse du login trahit l'existence du compte :
- Email inconnu → réponse immédiate (< 5 ms) — pas de BCrypt invoqué
- Email connu → réponse après ~300 ms — BCrypt vérifie le mot de passe
Cette différence mesurable est loggée dans activityLog de SecurityChaosService pour que le monitoring affiche l'attaque. Voir S6.
Codes d'erreur¶
| Code | Corps | Cause |
|---|---|---|
| 401 | {"error": "Identifiants incorrects"} |
Email inconnu ou mot de passe incorrect (clé auth.error.credentials) |
| 500 | {"error": "Erreur serveur"} |
Exception non gérée (clé auth.error.server) |
POST /api/auth/logout¶
Détruit la session utilisateur et invalide le securityToken associé.
Auth : session HTTP (pas d'erreur si déjà déconnecté)
Contrôleur : AuthController.logout()
Requête¶
Pas de corps.
Réponse — 200 OK¶
Le champ gracePeriodMs vaut 0 en comportement nominal. En Chaos Métier actif, il peut valoir 30000.
A11 — Token session non invalidé
En Chaos Métier niveau 3 et au-delà, le logout ne détruit pas la session : il se contente de retirer l'utilisateur de la clé LOGGED_IN_USER, mais le securityToken et le bundle de Chaos Scripting restent exploitables pendant 30 secondes. Un attaquant qui a intercepté le cookie peut rejouer une commande après le logout. Voir A11.
La valeur retournée dans gracePeriodMs indique la durée restante pendant laquelle l'ancienne session est encore acceptable. Cette valeur est intentionnellement exposée — elle permet aux scripts de test de vérifier que la faille est bien active.
GET /api/auth/status¶
Retourne l'état d'authentification de la session courante. Endpoint sans erreur — retourne authenticated: false si aucune session.
Auth : aucune (lecture seule)
Contrôleur : UserController.getStatus()
Réponse — 200 OK¶
| Champ | Description |
|---|---|
authenticated |
true si un utilisateur est connecté en session |
hasToken |
true si le securityToken checkout est encore valide |
userId |
ID de l'utilisateur courant ou null |
graceActive |
true si le Chaos Métier niveau ≥ 3 active la grace period A11 |
Cet endpoint est utilisé par le frontend pour décider s'il faut afficher la barre de navigation connectée, et par les outils de test pour vérifier l'état après logout.
GET /api/auth/me¶
Retourne le profil complet de l'utilisateur connecté.
Auth : session HTTP (obligatoire)
Contrôleur : UserController.getProfile()
Repository : UserRepository.findById() — relecture fraîche depuis la DB pour éviter les désynchros cache session / base.
Réponse — 200 OK (comportement nominal)¶
{
"id": 42,
"email": "alice@example.com",
"civility": "Mme",
"firstName": "Alice",
"lastName": "Durand",
"birthDate": "1990-05-15",
"phone": "0612345678",
"street": "12 rue de la Paix",
"postalCode": "75001",
"city": "Paris",
"region": "Île-de-France",
"country": "FR",
"address": "12 rue de la Paix"
}
Le champ address est un alias legacy de street — conservé pour la compatibilité avec les anciens clients frontend.
S3 — Hash BCrypt exposé
En Chaos Sécurité niveau 1 (Junior) et au-delà, la réponse inclut un champ password contenant le hash BCrypt complet de l'utilisateur :
Un attaquant peut alors tenter une attaque offline par dictionnaire. Voir S3.
Codes d'erreur¶
| Code | Corps | Cause |
|---|---|---|
| 401 | {"error": "Non authentifié"} |
Pas de session (clé auth.error.not_authenticated) |
PUT /api/auth/me¶
Met à jour le profil de l'utilisateur connecté. Validation stricte via ValidationService (formats nationaux de code postal, téléphone, etc.).
Auth : session HTTP (obligatoire)
Contrôleur : UserController.updateProfile()
Requête¶
PUT /api/auth/me HTTP/1.1
Content-Type: application/json
Cookie: JSESSIONID=...
{
"civility": "Mme",
"firstName": "Alice",
"lastName": "Durand",
"birthDate": "1990-05-15",
"phone": "0612345678",
"street": "12 rue de la Paix",
"postalCode": "75001",
"city": "Paris",
"region": "Île-de-France",
"country": "FR"
}
| Champ | Type | Contraintes |
|---|---|---|
civility |
string | M, Mme ou Mx |
firstName |
string | 2–100 caractères, sans caractères de contrôle |
lastName |
string | 2–100 caractères |
birthDate |
string (ISO) | YYYY-MM-DD, âge ≥ 16 et ≤ 120 |
phone |
string | Format national selon country (FR : 10 chiffres commençant par 0 non mobile, BE : 9 chiffres, etc.) |
street |
string | 5–200 caractères |
postalCode |
string | Format national (FR : 5 chiffres, BE : 4 chiffres, UK : SW1A 2AA, etc.) |
city |
string | 2–100 caractères, sans accents refusés |
region |
string | 2–100 caractères (optionnel) |
country |
string | Code ISO 3166-1 alpha-2 (FR, BE, DE…) |
Tous les champs sont optionnels : seuls ceux transmis sont mis à jour. Les champs absents du body ne sont pas touchés.
Réponse — 200 OK¶
{
"success": true,
"message": "Profil mis à jour",
"profile": { /* même structure que GET /me */ }
}
Codes d'erreur¶
| Code | Corps | Cause |
|---|---|---|
| 401 | {"error": "Non authentifié"} |
Pas de session |
| 422 | {"error": "Données invalides", "fields": {...}} |
Validation métier échouée. fields contient un mapping champ → message d'erreur localisé |
| 500 | {"error": "Erreur base de données...", "sql": "...", "chaos": true, "level": 3} |
DataIntegrityViolationException — souvent déclenchée par les injections SQL du Chaos Métier (champs postalCode ou country avec payload) |
Exemple de réponse 422¶
{
"error": "Données invalides",
"fields": {
"phone": "Format téléphone invalide pour FR — attendu : 10 chiffres commençant par 0 (ex: 0612345678)",
"postalCode": "Code postal FR invalide '7500' — attendu : 5 chiffres (ex: 75001)",
"birthDate": "Âge minimum requis : 16 ans (âge calculé : 14 ans)"
}
}
Chaque message provient d'une clé i18n dédiée dans messages_fr.properties (préfixe validation.*).
S9 — Mass Assignment
En Chaos Sécurité niveau 3 (Expert) et au-delà, les champs sensibles du body sont appliqués sans whitelist :
email→ réécrit l'email de l'utilisateurpassword→ réécrit le mot de passe en clair dans la colonnepassword(écrasant le hash BCrypt)
Ce side-effect est intentionnel : l'attaquant peut non seulement corrompre le compte, mais aussi verrouiller la victime (qui ne peut plus se connecter car BCrypt.matches() ne matche jamais un clair). La restauration nécessite une intervention admin via PUT /api/admin/accounts/{id}/password. Voir S9.
A9 — Log poisoning
En Chaos Métier niveau 3 et au-delà, les champs texte libres (firstName, lastName, street, city, phone, promoCode) sont insérés sans échappement dans les logs serveur. Un payload contenant \n[ERROR] Fake log peut créer des fausses entrées dans Loki. Voir A9.
Injection SQL via postalCode / country
En Chaos Métier niveau 2 et au-delà, les champs postalCode et country sont traités avec une concaténation SQL qui peut déclencher une DataIntegrityViolationException — visible dans la réponse 500 avec le détail sql exposé et le flag chaos: true.
Flux complet — Authentification et checkout¶
sequenceDiagram
autonumber
participant F as Frontend React
participant B as Backend Spring
participant DB as MySQL
F->>B: POST /api/auth/login { email, password }
B->>DB: SELECT * FROM users WHERE email=?
DB-->>B: User row
B->>B: BCrypt.matches()
B->>B: Crée HttpSession + SET LOGGED_IN_USER
B->>B: Génère securityToken (UUID)
B-->>F: 200 { securityToken, id, email, firstName, lastName }<br/>Set-Cookie: JSESSIONID=...
Note over F,B: L'utilisateur remplit son panier
F->>B: POST /api/checkout/address { ... }
B->>B: isLoggedIn(session)? + validateProfile()
B-->>F: 200 { step: "address", nextStep: "shipping" }
F->>B: POST /api/orders { securityToken, items }
B->>B: validateToken(session, providedToken)
B->>DB: INSERT INTO orders ...
B-->>F: 201 { success, orderNumber, orderId }
F->>B: POST /api/auth/logout
B->>B: session.invalidate()
B-->>F: 200 { message: "Deconnexion reussie" }
Liens associés¶
- Parcours checkout — utilisation du
securityToken - Chaos Sécurité — détails des failles S3, S6, S7, S9
- Chaos Métier — détails A9, A11
- Validation — règles de validation par pays