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"
}
| Claim | Description |
|---|---|
sub | ID unique de l'utilisateur (UUID) |
email | Email de l'utilisateur |
user_type | Rôle JWT : STUDENT, UNIVERSITY_ADMIN, ACADEMIC_SUPERVISOR, COMPANY, COMPANY_MENTOR, PLATFORM_ADMIN |
exp | Timestamp d'expiration |
type | Toujours "access" pour l'access token |
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"
| Claim | Description |
|---|---|
type | Toujours "refresh" pour le refresh token |
exp | Expire en 7 jours (hardcodé) |
Durées de vie des tokens
| Token | Durée | Configuration |
|---|---|---|
| access_token | ACCESS_TOKEN_EXPIRE_MINUTES (défaut: 30 min, 720 en local) | Variable .env |
| refresh_token | 7 jours | Hardcodé dans auth.py |
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
- Frontend envoie
email+password - Backend vérifie via Supabase Auth
- Backend génère access_token + refresh_token
- Backend retourne :
access_tokendans le corps JSONrefresh_tokendans un cookie httpOnly avec flagsSecureetSameSite=Strict
Requête authentifiée
- Frontend ajoute
Authorization: Bearer <access_token>au header - Backend vérifie le token (signature, expiration, type)
- Backend peut utiliser Redis pour cache (optionnel)
- Backend retourne les données
Refresh Token
- Le refresh_token est automatiquement envoyé via le cookie httpOnly
- Backend vérifie que c'est un refresh token valide
- Backend génère un nouvel access_token
- 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
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 :
| Token | Stockage | Pourquoi |
|---|---|---|
access_token | Mémoire JS (React state/context) | Inaccessible au JavaScript externe via XSS, disappear au refresh |
refresh_token | Cookie httpOnly | Impossible à 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
- Protection XSS : Le refresh_token ne peut pas être volé via XSS car il est en cookie httpOnly
- Auto-refresh : Le navigateur envoie automatiquement le cookie à chaque requête
- 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
ALGORITHMdans.env) - Secret :
SECRET_KEYdans.env
Endpoints d'authentification
| Endpoint | Méthode | Description |
|---|---|---|
/api/v1/auth/login | POST | Login avec email/password |
/api/v1/auth/login/oauth2 | POST | Login compatible OAuth2 |
/api/v1/auth/register | POST | Inscription |
/api/v1/auth/refresh-token | POST | Rafraîchir access token (via cookie httpOnly) |
/api/v1/auth/logout | POST | Logout (supprime le cookie refresh_token) |
/api/v1/auth/forgot-password | POST | Mot de passe oublié |
/api/v1/auth/reset-password | POST | Réinitialiser mot de passe |
Voir http://localhost:8000/docs pour la doc API complète.
Erreurs courantes
| Code | Cause |
|---|---|
| 401 | Token expiré, invalide, ou absent |
| 403 | Token valide mais pas assez de permissions |
Déboguer un problème d'auth
Vérifier un token
- Copier le token
- Aller sur jwt.io
- Coller le token
- Vérifier :
expn'est pas expirétypeest correct (accessourefresh)subcorrespond à 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
| Variable | Description |
|---|---|
SECRET_KEY | Clé de signature JWT |
ACCESS_TOKEN_EXPIRE_MINUTES | Durée access token |
REFRESH_TOKEN_EXPIRE_DAYS | Durée refresh token (pas utilisé, hardcodé à 7) |
ALGORITHM | Algo de signature (HS256) |
Limites connues
- Pas de révocation : Un token volé reste valide jusqu'à expiration (sauf si implémenté via Redis)
- Pas de refresh token rotation : Le refresh token n'est pas renouvelé à chaque utilisation — voir security-policy.md
- Cookie same-site : Le cookie utilise
SameSite=Strict, ce qui peut poser problème en développement local