`Cookie` en TypeScript

`Cookie` en TypeScript

Cet article explique Cookie en TypeScript.

Nous parcourons des modèles pratiques pour gérer les cookies de manière sûre et fiable à la fois dans le navigateur et sur le serveur.

YouTube Video

Cookie en TypeScript

Concepts de base des cookies

Un cookie est un mécanisme permettant de stocker de petites chaînes (paires nom=valeur) côté client, créé via l’en-tête HTTP Set-Cookie ou document.cookie. Vous pouvez contrôler leur comportement avec des attributs de sécurité (HttpOnly, Secure, SameSite, etc.).

Opérations de base dans le navigateur : document.cookie

Ci-dessous, l’exemple minimal pour écrire un cookie dans le navigateur. Créez un cookie en ajoutant une chaîne à document.cookie.

 1// Set a simple cookie that expires in 7 days.
 2// Note: Comments are in English per the user's preference.
 3const setSimpleCookie = (name: string, value: string) => {
 4  const days = 7;
 5  const expires = new Date(Date.now() + days * 86400_000).toUTCString();
 6  // name=value; Expires=...; Path=/
 7  document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; Expires=${expires}; Path=/`;
 8};
 9
10setSimpleCookie('theme', 'dark');
  • Ce code crée un cookie qui expire dans 7 jours. C’est facile à utiliser côté navigateur, mais comme vous ne pouvez pas définir HttpOnly, il faut éviter d’y stocker des informations sensibles.

Lecture des cookies (navigateur)

La fonction suivante est un utilitaire qui récupère, depuis document.cookie, la valeur du cookie portant un nom donné. document.cookie est renvoyé sous forme de chaîne, délimitée par un point-virgule et un espace ('; '). Par conséquent, nous la scindons et recherchons le cookie voulu.

 1// Parse document.cookie and return value for given name or null if not found.
 2const getCookie = (name: string): string | null => {
 3  const cookies = document.cookie ? document.cookie.split('; ') : [];
 4  for (const cookie of cookies) {
 5    const [k, ...rest] = cookie.split('=');
 6    const v = rest.join('=');
 7    if (decodeURIComponent(k) === name) {
 8      return decodeURIComponent(v);
 9    }
10  }
11  return null;
12};
13
14// Example usage:
15const theme = getCookie('theme'); // => "dark" if set
16console.log('theme cookie:', theme);
  • Cette fonction décode à la fois le nom et la valeur du cookie afin de les comparer et de les récupérer en toute sécurité. Elle gère les noms de cookies dupliqués et les caractères spéciaux encodés, ce qui en fait une implémentation simple mais robuste.

Comprendre et appliquer les attributs de sécurité

Les cookies possèdent plusieurs attributs importants, chacun contrôlant la sécurité et la portée de leur comportement.

  • L’attribut HttpOnly empêche l’accès au cookie depuis JavaScript, ce qui aide à atténuer les attaques XSS (cross-site scripting). Notez que vous ne pouvez pas définir HttpOnly côté navigateur.
  • L’attribut Secure limite l’envoi des cookies aux seules connexions HTTPS, réduisant le risque d’interception et d’altération.
  • L’attribut SameSite contrôle l’envoi des cookies lors des requêtes intersites et aide à prévenir les attaques CSRF (cross-site request forgery).
  • L’attribut Path précise la portée des chemins de requête pour lesquels le cookie est envoyé, et l’attribut Domain indique le domaine sur lequel le cookie est valide.
  • En définissant l’attribut Expires ou Max-Age, vous pouvez contrôler l’expiration du cookie.

Exemple d’ajout de SameSite / Secure dans le navigateur (ce qui est possible)

HttpOnly ne peut pas être défini côté client. Ci-dessous un exemple d’ajout de SameSite et Secure côté client. Cependant, Secure ne prend effet que sur les pages en HTTPS.

 1// Set cookie with SameSite and Secure attributes (Secure only effective over HTTPS).
 2const setCookieWithAttributes = (name: string, value: string) => {
 3  const maxAge = 60 * 60 * 24 * 7; // 7 days in seconds
 4  // Note: HttpOnly cannot be set from JS; set it on server-side when you want to restrict JS access.
 5  document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; `
 6    + `Max-Age=${maxAge}; `
 7    + `Path=/; `
 8    + `SameSite=Lax; `
 9    + `Secure`;
10};
11
12setCookieWithAttributes('session_hint', 'true');
  • SameSite=Lax est un choix sûr et courant. Pour des restrictions plus fortes, utilisez SameSite=Strict, mais des navigations légitimes depuis des sites externes peuvent cesser de fonctionner.

Définir des cookies côté serveur (Set-Cookie) (Node / TypeScript)

Côté serveur, vous pouvez ajouter HttpOnly via l’en-tête Set-Cookie ; la gestion de session devrait donc idéalement être effectuée côté serveur. Ci-dessous un exemple utilisant le module http de Node.

 1// A minimal Node HTTP server in TypeScript that sets a secure HttpOnly cookie.
 2// This example uses built-in 'crypto' for a random session id.
 3import http from 'http';
 4import crypto from 'crypto';
 5
 6const server = http.createServer((req, res) => {
 7  if (req.url === '/login') {
 8    const sessionId = crypto.randomBytes(16).toString('hex');
 9    // Set cookie with HttpOnly, Secure, SameSite and Path
10    // Expires is optional — Max-Age preferred for relative lifetimes.
11    res.setHeader('Set-Cookie', `sid=${sessionId}; `
12      + `HttpOnly; `
13      + `Secure; `
14      + `SameSite=Strict; `
15      + `Path=/; `
16      + `Max-Age=3600`
17    );
18    res.writeHead(302, { Location: '/' });
19    res.end();
20    return;
21  }
22
23  res.writeHead(200, { 'Content-Type': 'text/plain' });
24  res.end('Hello\n');
25});
26
27server.listen(3000, () => {
28  console.log('Server running on http://localhost:3000');
29});
  • Lorsque vous définissez l’attribut HttpOnly côté serveur, le cookie devient inaccessible depuis JavaScript, ce qui rend plus difficile son vol par des attaques XSS (cross-site scripting). De plus, en ajoutant l’attribut Secure afin que les cookies soient toujours envoyés via HTTPS, vous pouvez prévenir l’interception et l’altération et améliorer la sécurité des communications.

Implémentation de sérialisation/analyse des cookies (un utilitaire côté serveur)

Voici un utilitaire côté serveur simple qui construit une chaîne pour l’en-tête Set-Cookie.

 1// Cookie serialization helper for server-side use.
 2// Returns a properly formatted Set-Cookie header value.
 3type CookieOptions = {
 4  path?: string;
 5  domain?: string;
 6  maxAge?: number;
 7  expires?: Date;
 8  httpOnly?: boolean;
 9  secure?: boolean;
10  sameSite?: 'Strict' | 'Lax' | 'None';
11};
12
13const serializeCookie = (name: string, value: string, opts: CookieOptions = {}): string => {
14  const parts: string[] = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
15
16  if (opts.maxAge != null) parts.push(`Max-Age=${Math.floor(opts.maxAge)}`);
17  if (opts.expires) parts.push(`Expires=${opts.expires.toUTCString()}`);
18  if (opts.domain) parts.push(`Domain=${opts.domain}`);
19  parts.push(`Path=${opts.path ?? '/'}`);
20  if (opts.httpOnly) parts.push('HttpOnly');
21  if (opts.secure) parts.push('Secure');
22  if (opts.sameSite) parts.push(`SameSite=${opts.sameSite}`);
23
24  return parts.join('; ');
25};
26
27// Example usage:
28const headerValue = serializeCookie('uid', 'abc123', {
29  httpOnly: true,
30  secure: true,
31  sameSite: 'Lax',
32  maxAge: 3600
33});
34console.log(headerValue);
35// => "uid=abc123; Max-Age=3600; Path=/; HttpOnly; Secure; SameSite=Lax"
  • Cet utilitaire sert de base pour créer Set-Cookie sur un serveur personnalisé. Les bibliothèques peuvent gérer beaucoup plus de cas limites.

Prévenir la falsification grâce aux signatures (HMAC)

Il est dangereux de stocker des valeurs importantes directement dans des cookies. Nous présentons une méthode qui signe les valeurs côté serveur afin de détecter toute falsification. Ici nous utilisons HMAC-SHA256. En production, vous devez également gérer la révocation des jetons côté serveur.

 1// A simple HMAC signing and verification helper for cookies.
 2// Signing prevents client from tampering cookie values.
 3import crypto from 'crypto';
 4
 5const SECRET = 'replace_with_env_secret'; // store in env var in production
 6
 7const signValue = (value: string) => {
 8  const hmac = crypto.createHmac('sha256', SECRET);
 9  hmac.update(value);
10  return `${value}.${hmac.digest('hex')}`;
11};
12
13const verifySignedValue = (signed: string): string | null => {
14  const idx = signed.lastIndexOf('.');
15  if (idx === -1) return null;
16  const value = signed.slice(0, idx);
17  const sig = signed.slice(idx + 1);
18
19  const hmac = crypto.createHmac('sha256', SECRET);
20  hmac.update(value);
21  const expected = hmac.digest('hex');
22  // use timing-safe comparison in production
23  if (crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
24    return value;
25  }
26  return null;
27};
28
29// Example usage:
30const signed = signValue('userId=42');
31console.log('signed:', signed);
32console.log('verified:', verifySignedValue(signed)); // => "userId=42"
  • Grâce à cette méthode, vous pouvez vérifier si la valeur du cookie a été altérée. N'incluez pas la clé de signature dans le code source ; gérez-la par des moyens sécurisés tels que des variables d'environnement.

Cookies et atténuation CSRF (modèles généraux)

Les sessions qui utilisent des cookies nécessitent une protection CSRF. Les atténuations typiques incluent :.

  • Définissez SameSite sur Lax ou Strict pour empêcher les envois intersites inutiles. Utilisez SameSite=None; Secure uniquement lorsque la communication entre origines différentes est nécessaire.
  • Utilisez des jetons CSRF, validés par le serveur, pour les formulaires et les requêtes d'API. Ne stockez pas le jeton dans un cookie HttpOnly ; à la place, fournissez-le via le corps de la réponse ou des balises meta, et faites en sorte que JavaScript ne le lise que lorsque c'est nécessaire.
  • Si le jeton est accessible depuis JavaScript, veillez également à mettre en oeuvre des protections XSS (CSP, échappement des sorties, etc.).
  • Exigez une réauthentification pour les actions sensibles et lors de la connexion, et prévenez les attaques de fixation de session.

Ci-dessous, un exemple de client sécurisé qui envoie le jeton CSRF dans un en-tête. Cela suppose que le client a déjà reçu le jeton du serveur, par exemple dans le corps de la réponse.

 1// Example of sending CSRF token safely in a header using fetch.
 2// Assumes CSRF token was provided securely (e.g., via response body or meta tag).
 3async function sendProtectedRequest(url: string, csrfToken: string) {
 4  const res = await fetch(url, {
 5    method: 'POST',
 6    credentials: 'same-origin', // include cookies for same-site requests
 7    headers: {
 8      'Content-Type': 'application/json',
 9      'X-CSRF-Token': csrfToken
10    },
11    body: JSON.stringify({ action: 'doSomething' })
12  });
13  return res;
14}
  • Si vous spécifiez credentials: 'same-origin', seuls les cookies pour la même origine sont envoyés. Côté serveur, validez la valeur de l'en-tête X-CSRF-Token et vérifiez que le jeton correspond. Si des requêtes intersites sont nécessaires, configurez CORS avec soin.

Inter-site (CORS) et relation avec credentials

Pour envoyer et recevoir des cookies dans une communication entre origines différentes, le client doit spécifier credentials: 'include' et le serveur doit définir Access-Control-Allow-Credentials: true. Cependant, limitez cette configuration aux origines de confiance et n'utilisez pas Access-Control-Allow-Origin: *.

 1async function fetchCrossOriginData() {
 2  // Example: cross-origin fetch sending cookies.
 3  // Server must set Access-Control-Allow-Credentials: true
 4  // and a specific trusted origin.
 5  const res = await fetch('https://api.example.com/data', {
 6    credentials: 'include', // send cookies only to trusted domains
 7    method: 'GET'
 8  });
 9  return res;
10}
  • La combinaison de CORS et des cookies est très délicate du point de vue de la sécurité. Gérez strictement les origines autorisées à l'aide d'une liste blanche et évitez les communications intersites inutiles. De plus, lors de l'utilisation de SameSite=None; Secure, imposez HTTPS pour empêcher les attaques de l'homme du milieu.

Exemple : paramètres de cookie de session sécurisés avec Express (TypeScript)

En pratique, utilisez Express, la bibliothèque cookie, express-session, etc. Ci-dessous un exemple simple utilisant express et cookie-parser. En pratique, définissez secure à true et gérez le secret via des variables d’environnement.

 1// Express example using cookie-parser and setting a secure httpOnly cookie.
 2// npm install express cookie-parser @types/express @types/cookie-parser
 3import express from 'express';
 4import cookieParser from 'cookie-parser';
 5
 6const app = express();
 7app.use(cookieParser(process.env.COOKIE_SECRET));
 8
 9app.post('/login', (req, res) => {
10  // authenticate user (omitted)
11  const sessionId = 'generated-session-id';
12  res.cookie('sid', sessionId, {
13    httpOnly: true,
14    secure: true,        // require HTTPS in production
15    sameSite: 'lax',
16    maxAge: 1000 * 60 * 60 // 1 hour
17  });
18  res.json({ ok: true });
19});
20
21app.listen(3000);
  • En utilisant la fonctionnalité secret de cookieParser, vous pouvez manipuler facilement des cookies signés. Cependant, dans des applications réelles, pour des raisons de sécurité et de scalabilité, vous ne devriez pas stocker directement des données dans les cookies ; utilisez plutôt un magasin de session dédié.

Préfixes de cookie __Host- et __Secure-

Les navigateurs appliquent des règles spécifiques pour certains préfixes.

  • Le préfixe __Secure- Si le nom d’un cookie commence par __Secure-, l’attribut Secure est obligatoire.
  • Le préfixe __Host- S’il commence par __Host-, Secure est obligatoire, le chemin doit être / (racine) et Domain ne doit pas être défini.

Les utiliser réduit les erreurs de configuration et améliore la sécurité.

Bonnes pratiques concernant les cookies

Pour gérer les cookies en toute sécurité, tenez compte des points suivants.

  • Ne stockez pas d’informations sensibles directement dans des cookies. Préférez un identifiant de session côté serveur pour les jetons d’accès.
  • Définissez les cookies de session avec HttpOnly, Secure et SameSite=Lax (ou Strict).
  • Exploitez des préfixes comme __Host- et __Secure-.
  • Envisagez des signatures (HMAC) et le chiffrement pour empêcher la falsification et l’écoute.
  • Activez Secure et exigez HTTPS.
  • Visez le principe du moindre privilège et des expirations courtes.
  • Utilisez des jetons CSRF.
  • Attention aux différences de comportement de SameSite selon les navigateurs, en particulier les plus anciens.

Idées reçues courantes sur les cookies

Concernant les cookies, gardez à l’esprit les idées reçues suivantes.

  • 'Ajouter HttpOnly supprime l’impact des attaques XSS.' Bien que HttpOnly empêche l’accès aux cookies depuis JavaScript, des attaques XSS peuvent toujours être utilisées pour émettre des requêtes arbitraires. Vous devriez également utiliser la vérification des jetons CSRF, une CSP (Content Security Policy), et l’assainissement des entrées.
  • 'Secure est inutile en développement local.' Simuler le HTTPS et vérifier le comportement même en environnement local améliore la précision des tests. Au minimum, HTTPS doit être utilisé dans les environnements de préproduction et de production.
  • 'Des durées d’expiration longues sont pratiques.' Si vous définissez des durées de vie longues pour les cookies, la période pendant laquelle ils peuvent être exploités s’ils sont volés augmente. Vous pouvez définir des expirations plus courtes et intégrer une réauthentification périodique ainsi qu’une rotation des jetons.

Résumé

Bien que les cookies soient faciles à utiliser, une mauvaise gestion peut introduire des vulnérabilités de sécurité. Pour les gérer correctement avec TypeScript, il est important de comprendre des attributs comme HttpOnly, Secure et SameSite, et d’imposer des réglages sécurisés côté serveur. En n’y stockant pas directement de données sensibles et en combinant des signatures avec des expirations courtes, vous pouvez obtenir une gestion de session sûre et fiable.

Vous pouvez suivre l'article ci-dessus avec Visual Studio Code sur notre chaîne YouTube. Veuillez également consulter la chaîne YouTube.

YouTube Video