Aller au contenu principal

JWT et Cookies HttpOnly

Cette page documente l'implementation de l'authentification JWT avec cookies HttpOnly.

Vue d'ensemble

Le systeme utilise JWT (JSON Web Tokens) pour l'authentification, avec stockage des tokens dans des cookies HttpOnly pour une securite renforcee.

Pourquoi HttpOnly ?

Risques du stockage localStorage

RisqueDescription
XSSScripts malveillants peuvent lire localStorage
Vol de sessionTokens accessibles via JavaScript
Pas d'expiration autoTokens persistent meme apres fermeture

Avantages HttpOnly

AvantageDescription
Protection XSSCookies non accessibles via JavaScript
Expiration autoCookies peuvent expirer automatiquement
SameSiteProtection CSRF avec attribut SameSite

Architecture

┌─────────────────┐ ┌─────────────────┐ ┌──────────────┐
│ Frontend │ Login │ Backend │ Store │ Cookie │
│ React │ ------> │ Symfony │ ------> │ HttpOnly │
│ │ │ │ │ │
│ │ <------ │ │ <------ │ │
│ │ Set- │ │ │ │
│ │ Cookie │ │ │ │
└─────────────────┘ └─────────────────┘ └──────────────┘

Configuration

Backend (lexik_jwt_authentication.yaml)

lexik_jwt_authentication:
secret_key: '%env(resolve:JWT_SECRET_KEY)%'
public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
pass_phrase: '%env(JWT_PASSPHRASE)%'
token_ttl: 3600 # 1 heure

# Configuration cookies
set_cookies:
access_token:
name: jwt_access
httpOnly: true
secure: true
sameSite: strict
path: /
refresh_token:
name: jwt_refresh
httpOnly: true
secure: true
sameSite: strict
path: /api/token/refresh

Variables d'environnement

JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem
JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem
JWT_PASSPHRASE=your_passphrase
JWT_TOKEN_TTL=3600
JWT_REFRESH_TOKEN_TTL=2592000 # 30 jours

Flux d'authentification

Login

POST /api/auth/login
Content-Type: application/json

{
"username": "user@example.com",
"password": "password123"
}

Reponse

HTTP/1.1 200 OK
Set-Cookie: jwt_access=eyJ...; HttpOnly; Secure; SameSite=Strict; Path=/
Set-Cookie: jwt_refresh=abc...; HttpOnly; Secure; SameSite=Strict; Path=/api/token/refresh

{
"success": true,
"data": {
"user": {
"id": "...",
"email": "user@example.com",
"nom": "John Doe"
}
}
}

Requetes authentifiees

Les cookies sont automatiquement envoyes avec chaque requete :

fetch('/api/users/me', {
method: 'GET',
credentials: 'include' // Important !
})

Refresh Token

Endpoint

POST /api/token/refresh

Comportement

  1. Le cookie jwt_refresh est envoye automatiquement
  2. Le backend valide le refresh token
  3. Nouveaux cookies access + refresh sont generes

Rotation des tokens

A chaque refresh :

  • Nouveau access token genere
  • Nouveau refresh token genere (rotation)
  • Ancien refresh token invalide

Logout

Endpoint

POST /api/auth/logout

Comportement

  1. Suppression des cookies (Max-Age=0)
  2. Invalidation du refresh token en base
  3. Redirection vers la page de login

Securite

Attributs des cookies

AttributValeurDescription
HttpOnlytrueNon accessible via JavaScript
SecuretrueUniquement HTTPS
SameSiteStrictPas d'envoi cross-origin
Path/Chemin d'application
Max-Age3600Expiration en secondes

Protection CSRF

Le SameSite=Strict empeche l'envoi du cookie depuis d'autres domaines.

Pour les actions sensibles, un token CSRF additionnel peut etre utilise :

fetch('/api/action', {
method: 'POST',
headers: {
'X-CSRF-TOKEN': getCsrfToken()
},
credentials: 'include'
})

Frontend (Axios)

Configuration globale

import axios from 'axios';

const api = axios.create({
baseURL: '/api',
withCredentials: true, // Envoie les cookies
});

// Intercepteur pour refresh automatique
api.interceptors.response.use(
response => response,
async error => {
if (error.response?.status === 401) {
try {
await api.post('/token/refresh');
return api.request(error.config);
} catch (refreshError) {
// Redirection login
window.location.href = '/#/auth/login';
}
}
return Promise.reject(error);
}
);

Gestion des erreurs

ErreurHTTPDescription
Token expire401Access token expire, refresh necessaire
Token invalide401Token corrompu ou manipule
Refresh expire401Session terminee, login necessaire
CSRF invalide403Token CSRF manquant ou invalide

Migration depuis localStorage

Si vous migrez depuis localStorage :

  1. Phase 1 : Acceptez les deux modes (localStorage + cookies)
  2. Phase 2 : Migrez les tokens existants vers cookies
  3. Phase 3 : Supprimez le support localStorage
// Verification migration
if (localStorage.getItem('token')) {
// Ancien mode - forcer re-login
localStorage.removeItem('token');
window.location.href = '/#/auth/login';
}

Bonnes pratiques

:::tip Recommandations

  1. HTTPS obligatoire : Les cookies Secure ne fonctionnent qu'en HTTPS
  2. SameSite=Strict : Utilisez Strict sauf besoin specifique
  3. Duree courte : Access token court (1h), refresh token long (30j)
  4. Rotation : Rotez les refresh tokens a chaque utilisation
  5. Logout propre : Invalidez les tokens cote serveur au logout :::