`Cookie` in TypeScript

`Cookie` in TypeScript

Dit artikel legt Cookie in TypeScript uit.

We bespreken praktische patronen om cookies veilig en betrouwbaar te behandelen, zowel in de browser als op de server.

YouTube Video

Cookie in TypeScript

Basisconcepten van cookies

Een cookie is een mechanisme om kleine tekenreeksen (naam=waarde-paren) op de client op te slaan, aangemaakt via de HTTP-header Set-Cookie of document.cookie. Je kunt hun gedrag sturen met beveiligingsattributen (HttpOnly, Secure, SameSite, enz.).

Basisbewerkingen in de browser: document.cookie

Hieronder staat het minimale voorbeeld om een cookie in de browser te schrijven. Maak een cookie aan door een tekenreeks toe te voegen aan 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');
  • Deze code maakt een cookie aan die over 7 dagen verloopt. Het is aan de browserkant eenvoudig te gebruiken, maar omdat je HttpOnly niet kunt instellen, moet je vermijden gevoelige informatie op te slaan.

Cookies lezen (browser)

De volgende functie is een hulpfunctie die de waarde van de cookie met een opgegeven naam ophaalt uit document.cookie. document.cookie wordt geretourneerd als een tekenreeks waarin items zijn gescheiden door een puntkomma en een spatie ('; '). Daarom splitsen we die en zoeken we naar de gewenste cookie.

 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);
  • Deze functie decodeert zowel de cookienaam als de waarde om ze veilig te vergelijken en op te halen. Deze functie kan overweg met dubbele cookienamen en gecodeerde speciale tekens, waardoor het een eenvoudige maar robuuste implementatie is.

Beveiligingsattributen begrijpen en toepassen

Cookies hebben verschillende belangrijke attributen, die elk de beveiliging en de reikwijdte van het gedrag bepalen.

  • Het attribuut HttpOnly voorkomt dat de cookie toegankelijk is vanuit JavaScript, wat helpt XSS-aanvallen (cross-site scripting) te beperken. Let op: je kunt HttpOnly niet vanuit de browser instellen.
  • Het attribuut Secure zorgt ervoor dat cookies alleen via HTTPS worden verzonden, waardoor het risico op afluisteren en manipulatie afneemt.
  • Het attribuut SameSite bepaalt of cookies met cross-siteverzoeken worden meegestuurd en helpt CSRF-aanvallen (cross-site request forgery) te voorkomen.
  • Het attribuut Path specificeert de reikwijdte van verzoekpaden waarvoor de cookie wordt meegestuurd, en het attribuut Domain specificeert het domein waarop de cookie geldig is.
  • Door het attribuut Expires of Max-Age in te stellen, kun je de vervaldatum van de cookie bepalen.

Voorbeeld van het toevoegen van SameSite / Secure in de browser (wat mogelijk is)

HttpOnly kan niet aan de clientzijde worden ingesteld. Hieronder staat een voorbeeld van het toevoegen van SameSite en Secure aan de clientzijde. Echter, Secure werkt alleen op HTTPS-pagina's.

 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 is een veilige en gangbare keuze. Gebruik voor strengere beperkingen SameSite=Strict, maar legitieme navigaties vanaf externe sites kunnen dan niet meer werken.

Cookies instellen op de server (Set-Cookie) (Node / TypeScript)

Op de server kun je HttpOnly toevoegen via de Set-Cookie-header; sessiebeheer gebeurt idealiter aan de serverkant. Hieronder staat een voorbeeld met de http-module van 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});
  • Wanneer je het attribuut HttpOnly aan de serverzijde instelt, wordt de cookie ontoegankelijk vanuit JavaScript, waardoor het voor XSS-aanvallen (cross-site scripting) moeilijker wordt om deze te stelen. Daarnaast kun je door het attribuut Secure toe te voegen zodat cookies altijd via HTTPS worden verzonden, afluisteren en manipulatie voorkomen en de beveiliging van de communicatie verbeteren.

Implementatie voor serialiseren/parseren van cookies (een helper aan serverzijde)

Hieronder staat een eenvoudige tool aan de serverzijde die een Set-Cookie-headerstring opbouwt.

 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"
  • Deze utility vormt de basis voor het aanmaken van Set-Cookie op een aangepaste server. Bibliotheken kunnen veel meer randgevallen afhandelen.

Manipulatie voorkomen met handtekeningen (HMAC)

Het is gevaarlijk om belangrijke waarden rechtstreeks in cookies op te slaan. We introduceren een methode die waarden op de server ondertekent om manipulatie te detecteren. Hier gebruiken we HMAC-SHA256. In productie moet je ook het intrekken van tokens aan de serverzijde beheren.

 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"
  • Met deze methode kun je controleren of de Cookie-waarde is gemanipuleerd. Neem de ondertekeningssleutel niet op in de broncode; beheer deze via veilige middelen zoals omgevingsvariabelen.

Cookies en CSRF-mitigatie (algemene patronen)

Sessies die cookies gebruiken, hebben CSRF-bescherming nodig. Typische mitigaties zijn onder meer:.

  • Stel SameSite in op Lax of Strict om onnodig verzenden tussen sites te voorkomen. Gebruik SameSite=None; Secure alleen wanneer cross-origin-communicatie noodzakelijk is.
  • Gebruik CSRF-tokens, gevalideerd door de server, voor formulieren en API-verzoeken. Sla het token niet op in een HttpOnly-cookie; lever het in plaats daarvan via de body van de response of metatags aan, en laat JavaScript het alleen lezen wanneer dat nodig is.
  • Als het token vanuit JavaScript toegankelijk is, implementeer dan ook XSS-bescherming (CSP, output-escaping, enz.).
  • Vereis herauthenticatie voor gevoelige acties en bij het inloggen, en voorkom sessiefixatie-aanvallen.

Hieronder staat een veilig clientvoorbeeld dat het CSRF-token in een header verstuurt. Dit gaat ervan uit dat de client het token al van de server heeft ontvangen, bijv. in de body van de response.

 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}
  • Als je credentials: 'same-origin' opgeeft, worden alleen cookies voor dezelfde origin verzonden. Valideer op de server de waarde van de X-CSRF-Token-header en verifieer dat het token overeenkomt. Als cross-site-verzoeken noodzakelijk zijn, configureer CORS zorgvuldig.

Cross-site (CORS) en de relatie met credentials

Om cookies te verzenden en te ontvangen bij cross-origin-communicatie, moet de client credentials: 'include' opgeven en moet de server Access-Control-Allow-Credentials: true instellen. Beperk deze configuratie echter tot vertrouwde origins en gebruik geen 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}
  • De combinatie van CORS en cookies is zeer kwetsbaar vanuit een beveiligingsperspectief. Beheer toegestane origins strikt met een whitelist en vermijd onnodige cross-site-communicatie. Dwing bovendien bij gebruik van SameSite=None; Secure HTTPS af om man-in-the-middle-aanvallen te voorkomen.

Voorbeeld: veilige instellingen voor sessiecookies met Express (TypeScript)

In de praktijk gebruik je Express, de cookie-bibliotheek, express-session, enzovoort. Hieronder staat een eenvoudig voorbeeld met express en cookie-parser. Stel in de praktijk secure in op true en beheer de secret via omgevingsvariabelen.

 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);
  • Door de secret-functie van cookieParser te gebruiken, kun je gemakkelijk met ondertekende cookies werken. In echte toepassingen moet je vanuit het oogpunt van veiligheid en schaalbaarheid geen gegevens rechtstreeks in cookies opslaan; gebruik in plaats daarvan een speciale sessieopslag.

Cookieprefixen __Host- en __Secure-

Browsers handhaven speciale regels voor bepaalde prefixen.

  • De prefix __Secure- Als een cookienaam begint met __Secure-, is het attribuut Secure verplicht.
  • De prefix __Host- Als de naam begint met __Host-, is Secure vereist, moet het pad / (root) zijn en mag Domain niet worden ingesteld.

Het gebruik hiervan vermindert misconfiguraties en verbetert de beveiliging.

Best practices voor cookies

Om Cookies veilig te verwerken, houd rekening met de volgende punten.

  • Sla geen gevoelige informatie rechtstreeks in cookies op. Geef de voorkeur aan een sessie-ID aan de serverzijde voor access tokens.
  • Stel sessiecookies in met HttpOnly, Secure en SameSite=Lax (of Strict).
  • Maak gebruik van prefixen zoals __Host- en __Secure-.
  • Overweeg handtekeningen (HMAC) en versleuteling om manipulatie en afluisteren te voorkomen.
  • Schakel Secure in en vereis HTTPS.
  • Streef naar minimale privileges en korte vervaltijden.
  • Gebruik CSRF-tokens.
  • Let op verschillen in SameSite-gedrag tussen browsers, vooral oudere.

Veelvoorkomende misvattingen over cookies

Wat cookies betreft, houd rekening met de volgende veelvoorkomende misvattingen.

  • 'HttpOnly toevoegen neemt de impact van XSS weg.' Hoewel HttpOnly voorkomt dat cookies door JavaScript worden benaderd, kan XSS nog steeds worden gebruikt om willekeurige verzoeken uit te voeren. Je zou ook CSRF-tokenverificatie, CSP (Content Security Policy) en invoersanitisatie moeten toepassen.
  • 'Secure is onnodig voor lokale ontwikkeling.' Het simuleren van HTTPS en het verifiëren van gedrag, ook in lokale omgevingen, verbetert de testnauwkeurigheid. Op zijn minst moet HTTPS worden gebruikt in staging- en productieomgevingen.
  • 'Lange vervaltermijnen zijn handig.' Als je lange levensduren voor cookies instelt, neemt de periode toe waarin ze kunnen worden misbruikt als ze worden gestolen. Je kunt kortere vervaltermijnen instellen en periodieke herauthenticatie en tokenrotatie toepassen.

Samenvatting

Hoewel cookies eenvoudig te gebruiken zijn, kan onjuist gebruik beveiligingslekken introduceren. Om ze correct te beheren met TypeScript is het belangrijk om attributen zoals HttpOnly, Secure en SameSite te begrijpen en veilige server-side instellingen af te dwingen. Door geen gevoelige gegevens rechtstreeks op te slaan en handtekeningen te combineren met korte vervaltijden, kun je veilig en betrouwbaar sessiebeheer realiseren.

Je kunt het bovenstaande artikel volgen met Visual Studio Code op ons YouTube-kanaal. Bekijk ook het YouTube-kanaal.

YouTube Video