Aller au contenu principal

Authentification (JWT)

Ce fichier documente le système d'authentification du backend StageConnect.

Vue d'ensemble

Le backend utilise des JWT (JSON Web Tokens) pour l'authentification.

Stockage sécurisé des tokens :

  • Access token : retourné dans le corps de la réponse JSON
  • Refresh token : stocké dans un cookie httpOnly (non accessible via JavaScript)

Cette architecture protège contre les attaques XSS : même si un script malveillant exécute dans le navigateur, il ne peut pas voler le refresh_token car il est dans un cookie httpOnly.

Redis est utilisé pour le cache uniquement — pas pour stocker les sessions. Les JWT sont stateless.

Structure d'un JWT

Access Token

{
"sub": "user_id_abc123",
"email": "student@example.com",
"user_type": "STUDENT",
"exp": 1740000000,
"type": "access"
}
ClaimDescription
subID unique de l'utilisateur (UUID)
emailEmail de l'utilisateur
user_typeRôle JWT : STUDENT, UNIVERSITY_ADMIN, ACADEMIC_SUPERVISOR, COMPANY, COMPANY_MENTOR, PLATFORM_ADMIN
expTimestamp d'expiration
typeToujours "access" pour l'access token
user_type JWT vs user_type DB

Les valeurs JWT (UNIVERSITY_ADMIN, etc.) diffèrent des valeurs en base de données (ACADEMIC_STAFF, etc.). La correspondance est gérée lors de la création du token. Voir Permissions & Contrôle d'Accès pour le détail complet.

Refresh Token

{
"sub": "user_id_abc123",
"email": "student@example.com",
"user_type": "STUDENT",
"exp": 1740070000,
"type": "refresh"
ClaimDescription
typeToujours "refresh" pour le refresh token
expExpire en 7 jours (hardcodé)

Durées de vie des tokens

TokenDuréeConfiguration
access_tokenACCESS_TOKEN_EXPIRE_MINUTES (défaut: 30 min, 720 en local)Variable .env
refresh_token7 joursHardcodé dans auth.py
remarque

La valeur par défaut du code est 30 minutes. Les devs utilisent 720 en local (confort). En production, la valeur est 30 minutes.

Flow d'authentification

Login

  1. Frontend envoie email + password
  2. Backend vérifie via Supabase Auth
  3. Backend génère access_token + refresh_token
  4. Backend retourne :
    • access_token dans le corps JSON
    • refresh_token dans un cookie httpOnly avec flags Secure et SameSite=Strict

Requête authentifiée

  1. Frontend ajoute Authorization: Bearer <access_token> au header
  2. Backend vérifie le token (signature, expiration, type)
  3. Backend peut utiliser Redis pour cache (optionnel)
  4. Backend retourne les données

Refresh Token

  1. Le refresh_token est automatiquement envoyé via le cookie httpOnly
  2. Backend vérifie que c'est un refresh token valide
  3. Backend génère un nouvel access_token
  4. Backend retourne le nouveau access_token (et renouvelle le cookie)

Logout

Le logout est géré via le endpoint /api/v1/auth/logout :

  • Supprimer l'access_token du storage local (mémorie JS)
  • Le backend supprime le cookie refresh_token via Set-Cookie: refresh_token=; Max-Age=0
Pas de révocation de tokens

Le système actuel ne permet pas de révoquer un token avant son expiration. Si un token est volé, il reste valide jusqu'à exp. C'est une lacune connue — voir security-policy.md.

Stockage des tokens (côté client)

Le backend utilise une architecture hybride pour optimiser sécurité et UX :

TokenStockagePourquoi
access_tokenMémoire JS (React state/context)Inaccessible au JavaScript externe via XSS, disappear au refresh
refresh_tokenCookie httpOnlyImpossible à lire depuis le JS, protection XSS, automatiquement envoyé

::: warning localStorage vulnérable NE PAS stocker le refresh_token dans localStorage. Le localStorage est vulnérable aux attaques XSS — un script malveillant peut lire le token. Le cookie httpOnly est la seule méthode sécurisée. :::

Avantages de cette architecture

  1. Protection XSS : Le refresh_token ne peut pas être volé via XSS car il est en cookie httpOnly
  2. Auto-refresh : Le navigateur envoie automatiquement le cookie à chaque requête
  3. UX : L'utilisateur reste connecté automatiquement

Sécurité

Hachage des mots de passe

  • Algorithme : bcrypt
  • Limite : 72 bytes (tronqué si plus long)
  • Coût : défaut bcrypt (~4ms)

Vérification des tokens

# backend/app/utils/auth.py
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])

# Vérifie signature + expiration
if payload.get("type") != token_type:
raise 401 # Wrong token type

Algorithme

  • Algorithm : HS256 (configurable via ALGORITHM dans .env)
  • Secret : SECRET_KEY dans .env

Endpoints d'authentification

EndpointMéthodeDescription
/api/v1/auth/loginPOSTLogin avec email/password
/api/v1/auth/login/oauth2POSTLogin compatible OAuth2
/api/v1/auth/registerPOSTInscription
/api/v1/auth/refresh-tokenPOSTRafraîchir access token (via cookie httpOnly)
/api/v1/auth/logoutPOSTLogout (supprime le cookie refresh_token)
/api/v1/auth/forgot-passwordPOSTMot de passe oublié
/api/v1/auth/reset-passwordPOSTRéinitialiser mot de passe

Voir http://localhost:8000/docs pour la doc API complète.

Erreurs courantes

CodeCause
401Token expiré, invalide, ou absent
403Token valide mais pas assez de permissions

Déboguer un problème d'auth

Vérifier un token

  1. Copier le token
  2. Aller sur jwt.io
  3. Coller le token
  4. Vérifier :
    • exp n'est pas expiré
    • type est correct (access ou refresh)
    • sub correspond à l'utilisateur attendu

Vérifier Redis

# Voir les clés de cache
redis-cli KEYS "cache:*"

# Voir une valeur spécifique
redis-cli GET "cache:dashboard:user_id"

Logs

Les erreurs d'auth sont logguées avec le niveau appropriate. Vérifier les logs du backend pour JWTError ou credentials_exception.

Variables d'environnement liées

VariableDescription
SECRET_KEYClé de signature JWT
ACCESS_TOKEN_EXPIRE_MINUTESDurée access token
REFRESH_TOKEN_EXPIRE_DAYSDurée refresh token (pas utilisé, hardcodé à 7)
ALGORITHMAlgo de signature (HS256)

Limites connues

  1. Pas de révocation : Un token volé reste valide jusqu'à expiration (sauf si implémenté via Redis)
  2. Pas de refresh token rotation : Le refresh token n'est pas renouvelé à chaque utilisation — voir security-policy.md
  3. Cookie same-site : Le cookie utilise SameSite=Strict, ce qui peut poser problème en développement local