Backend changes (Action 5.1.1.1): - Set access_token cookie in Login, Register, and Refresh handlers - Cookie uses same configuration as refresh_token (httpOnly, Secure, SameSite) - Expiry matches AccessTokenTTL (5 minutes) - Update logout handler to clear access_token cookie Backend middleware (Action 5.1.1.1): - Update auth middleware to read access token from cookie first - Fallback to Authorization header for backward compatibility - Update OptionalAuth with same cookie-first logic Frontend changes (Actions 5.1.1.2 & 5.1.1.3): - Remove localStorage token storage from TokenStorage service - TokenStorage now returns null for getAccessToken/getRefreshToken (httpOnly cookies not accessible) - Remove Authorization header logic from API client - Remove token expiration checks (can't check httpOnly cookies from JS) - Update AuthContext to remove localStorage usage - Update tokenRefresh to work without reading tokens from JS - Simplify refresh logic: periodic refresh every 4 minutes (no expiration checks) Security improvements: - Access tokens no longer exposed to XSS attacks (httpOnly cookies) - Tokens automatically sent with requests via withCredentials: true - Backend reads tokens from cookies, not Authorization headers - All users will need to re-login after deployment (breaking change) Breaking change: All users must re-login after deployment
107 lines
4 KiB
TypeScript
107 lines
4 KiB
TypeScript
/**
|
|
* TokenStorage - Service de gestion du stockage des tokens
|
|
* T0169: Service simple pour stocker, récupérer et supprimer les tokens d'authentification
|
|
*
|
|
* SECURITY: Migration vers cookies httpOnly COMPLÉTÉE (Action 5.1.1.2)
|
|
* - Access Token: Stocké dans un cookie httpOnly par le backend (pas accessible via JavaScript)
|
|
* - Refresh Token: Stocké dans un cookie httpOnly par le backend (pas accessible via JavaScript)
|
|
*
|
|
* IMPORTANT: Les tokens sont maintenant dans des cookies httpOnly set par le backend.
|
|
* JavaScript ne peut pas accéder aux cookies httpOnly, donc cette classe est maintenant
|
|
* principalement une API de compatibilité (no-op).
|
|
*
|
|
* NOTE: Les cookies httpOnly sont automatiquement envoyés avec les requêtes via withCredentials: true.
|
|
* Le backend lit les tokens depuis les cookies, pas depuis localStorage ou les headers Authorization.
|
|
*/
|
|
|
|
const ACCESS_TOKEN_KEY = 'veza_access_token';
|
|
const REFRESH_TOKEN_KEY = 'veza_refresh_token';
|
|
|
|
/**
|
|
* Réinitialise le stockage (utile pour les tests)
|
|
* @internal
|
|
*/
|
|
export function _resetTokenMemory(): void {
|
|
// No-op: tokens are in httpOnly cookies, not accessible from JS
|
|
}
|
|
|
|
/**
|
|
* Classe TokenStorage pour gérer le stockage des tokens
|
|
* T0169: Service de gestion du stockage tokens
|
|
*
|
|
* SECURITY: Action 5.1.1.2 - Tokens sont maintenant dans des cookies httpOnly
|
|
* Cette classe est maintenant principalement une API de compatibilité (no-op)
|
|
* car les cookies httpOnly ne sont pas accessibles via JavaScript.
|
|
*/
|
|
export class TokenStorage {
|
|
/**
|
|
* Stocke les tokens d'authentification
|
|
* SECURITY: Action 5.1.1.2 - No-op car les tokens sont dans des cookies httpOnly
|
|
* Le backend sette les cookies httpOnly lors du login/register/refresh.
|
|
*
|
|
* @param accessToken - Token d'accès JWT (ignoré, dans cookie httpOnly)
|
|
* @param refreshToken - Token de rafraîchissement (ignoré, dans cookie httpOnly)
|
|
*/
|
|
static setTokens(_accessToken: string, _refreshToken: string): void {
|
|
// No-op: tokens are set in httpOnly cookies by backend, not accessible to JS
|
|
// Clean up any legacy localStorage tokens if they exist
|
|
try {
|
|
localStorage.removeItem(ACCESS_TOKEN_KEY);
|
|
localStorage.removeItem(REFRESH_TOKEN_KEY);
|
|
} catch {
|
|
// Ignore errors (e.g., in SSR environment)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Récupère le token d'accès
|
|
* SECURITY: Action 5.1.1.2 - Retourne null car le token est dans un cookie httpOnly
|
|
* Les cookies httpOnly ne sont pas accessibles via JavaScript.
|
|
*
|
|
* @returns null (token est dans cookie httpOnly, non accessible)
|
|
*/
|
|
static getAccessToken(): string | null {
|
|
// Token is in httpOnly cookie, not accessible from JavaScript
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Récupère le token de rafraîchissement
|
|
* SECURITY: Action 5.1.1.2 - Retourne null car le token est dans un cookie httpOnly
|
|
* Les cookies httpOnly ne sont pas accessibles via JavaScript.
|
|
*
|
|
* @returns null (token est dans cookie httpOnly, non accessible)
|
|
*/
|
|
static getRefreshToken(): string | null {
|
|
// Token is in httpOnly cookie, not accessible from JavaScript
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Supprime tous les tokens
|
|
* SECURITY: Action 5.1.1.2 - Nettoie seulement localStorage legacy si présent
|
|
* Les cookies httpOnly sont supprimés par le backend lors du logout.
|
|
*/
|
|
static clearTokens(): void {
|
|
// Clean up any legacy localStorage tokens if they exist
|
|
try {
|
|
localStorage.removeItem(ACCESS_TOKEN_KEY);
|
|
localStorage.removeItem(REFRESH_TOKEN_KEY);
|
|
} catch {
|
|
// Ignore errors (e.g., in SSR environment)
|
|
}
|
|
// Cookies httpOnly are cleared by backend on logout
|
|
}
|
|
|
|
/**
|
|
* Vérifie si des tokens sont présents
|
|
* SECURITY: Action 5.1.1.2 - Retourne false car on ne peut pas vérifier les cookies httpOnly
|
|
* Les cookies httpOnly ne sont pas accessibles via JavaScript.
|
|
*
|
|
* @returns false (ne peut pas vérifier les cookies httpOnly depuis JS)
|
|
*/
|
|
static hasTokens(): boolean {
|
|
// Cannot check httpOnly cookies from JavaScript
|
|
return false;
|
|
}
|
|
}
|