`Cookie` in TypeScript

`Cookie` in TypeScript

This article explains Cookie in TypeScript.

We walk through practical patterns for handling cookies safely and reliably in both the browser and the server.

YouTube Video

Cookie in TypeScript

Basic concepts of cookies

A cookie is a mechanism for storing small strings (name=value pairs) on the client, created via the Set-Cookie HTTP header or document.cookie. You can control their behavior with security attributes (HttpOnly, Secure, SameSite, etc.).

Basic operations in the browser: document.cookie

Below is the minimal example to write a cookie in the browser. Create a cookie by appending a string to 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');
  • This code creates a cookie that expires in 7 days. It’s easy to use on the browser side, but since you can’t set HttpOnly, you should avoid storing sensitive information.

Reading cookies (browser)

The following function is a helper that retrieves the value of the cookie with a specified name from document.cookie. document.cookie is returned as a string delimited by a semicolon and a space ('; '). Therefore, we split it and look for the desired 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);
  • This function decodes both the cookie name and value to safely compare and retrieve them. It handles duplicate cookie names and encoded special characters, making it a simple yet robust implementation.

Understanding and applying secure attributes

Cookies have several important attributes, each controlling security and scope of behavior.

  • The HttpOnly attribute prevents the cookie from being accessible from JavaScript, helping to mitigate XSS (cross-site scripting) attacks. Note that you cannot set HttpOnly from the browser side.
  • The Secure attribute restricts cookies to be sent only over HTTPS, reducing the risk of eavesdropping and tampering.
  • The SameSite attribute controls whether cookies are sent with cross-site requests and helps prevent CSRF (cross-site request forgery) attacks.
  • The Path attribute specifies the scope of request paths for which the cookie is sent, and the Domain attribute specifies the domain where the cookie is valid.
  • By setting the Expires or Max-Age attribute, you can control the cookie's expiration.

Example of adding SameSite / Secure in the browser (what's possible)

HttpOnly cannot be set on the client side. Below is an example of adding SameSite and Secure on the client side. However, Secure only takes effect on HTTPS pages.

 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 a safe and common choice. For stronger restrictions use SameSite=Strict, but legitimate navigations from external sites may stop working.

Setting cookies on the server (Set-Cookie) (Node / TypeScript)

On the server you can add HttpOnly via the Set-Cookie header, so session management should ideally be done server-side. Below is an example using Node's http module.

 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});
  • When you set the HttpOnly attribute on the server side, the cookie becomes inaccessible from JavaScript, making it harder for XSS (cross-site scripting) attacks to steal it. Additionally, by adding the Secure attribute so that cookies are always sent over HTTPS, you can prevent eavesdropping and tampering and improve communication security.

Cookie serialization/parsing implementation (a server-side helper)

Below is a simple server-side utility that builds a Set-Cookie header string.

 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"
  • This utility forms the basis for creating Set-Cookie on a custom server. Libraries can handle many more edge cases.

Preventing tampering with signatures (HMAC)

It's dangerous to store important values directly in cookies. We introduce a method that signs values on the server to detect tampering. Here we use HMAC-SHA256. In production, you also need to manage token revocation on the server side.

 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"
  • With this method, you can check whether the Cookie value has been tampered with. Do not include the signing key in the source code; manage it through secure means such as environment variables.

Cookies and CSRF mitigation (general patterns)

Sessions that use cookies need CSRF protection. Typical mitigations include:.

  • Set SameSite to Lax or Strict to prevent unnecessary cross-site sending. Use SameSite=None; Secure only when cross-origin communication is necessary.
  • Use CSRF tokens, validated by the server, for forms and API requests. Do not store the token in an HttpOnly cookie; instead, provide it via the response body or meta tags, and have JavaScript read it only when necessary.
  • If the token can be accessed from JavaScript, be sure to also implement XSS protections (CSP, output escaping, etc.).
  • Require reauthentication for sensitive actions and at login, and prevent session fixation attacks.

Below is a safe client example that sends the CSRF token in a header. This assumes the client has already received the token from the server, e.g., in the response body.

 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}
  • If you specify credentials: 'same-origin', only cookies for the same origin are sent. On the server, validate the value of the X-CSRF-Token header and verify that the token matches. If cross-site requests are necessary, configure CORS carefully.

Cross-site (CORS) and the relationship with credentials

To send and receive cookies in cross-origin communication, the client must specify credentials: 'include' and the server must set Access-Control-Allow-Credentials: true. However, restrict this configuration to trusted origins and do not use 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}
  • The combination of CORS and cookies is very delicate from a security perspective. Manage allowed origins strictly with a whitelist and avoid unnecessary cross-site communication. Also, when using SameSite=None; Secure, enforce HTTPS to prevent man-in-the-middle attacks.

Example: secure session cookie settings with Express (TypeScript)

In practice, use Express, the cookie library, express-session, and so on. Below is a simple example using express and cookie-parser. In practice, set secure to true and manage the secret via environment variables.

 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);
  • By using cookieParser's secret feature, you can easily work with signed cookies. However, in real applications, from the perspectives of security and scalability, you should not store data directly in cookies; instead, use a dedicated session store.

Cookie prefixes __Host- and __Secure-

Browsers enforce special rules for certain prefixes.

  • The __Secure- prefix If a cookie name starts with __Secure-, the Secure attribute is required.
  • The __Host- prefix If it starts with __Host-, Secure is required, the path must be / (root), and Domain must not be set.

Using these reduces misconfiguration and improves security.

Cookie best practices

To handle Cookies securely, consider the following points.

  • Do not store sensitive information directly in cookies. Prefer a server-side session ID for access tokens.
  • Set session cookies with HttpOnly, Secure, and SameSite=Lax (or Strict).
  • Leverage prefixes like __Host- and __Secure-.
  • Consider signatures (HMAC) and encryption to prevent tampering and eavesdropping.
  • Enable Secure and require HTTPS.
  • Aim for least privilege and short expirations.
  • Use CSRF tokens.
  • Beware of differences in SameSite behavior across browsers, especially older ones.

Common misconceptions about cookies

Regarding cookies, be mindful of the following common misconceptions.

  • 'Adding HttpOnly removes the impact of XSS.' While HttpOnly prevents cookies from being accessed by JavaScript, XSS can still be used to issue arbitrary requests. You should also employ CSRF token verification, CSP (Content Security Policy), and input sanitization.
  • 'Secure is unnecessary for local development.' Simulating HTTPS and verifying behavior even in local environments improves test accuracy. At a minimum, HTTPS should be used in staging and production environments.
  • 'Long expiration periods are convenient.' If you set long cookie lifetimes, the period during which they can be abused if stolen increases. You can set shorter expirations and incorporate periodic re-authentication and token rotation.

Summary

While cookies are easy to use, mishandling them can introduce security vulnerabilities. To manage them properly with TypeScript, it's important to understand attributes like HttpOnly, Secure, and SameSite, and enforce secure server-side settings. By not storing sensitive data directly and combining signatures with short expirations, you can achieve safe and reliable session management.

You can follow along with the above article using Visual Studio Code on our YouTube channel. Please also check out the YouTube channel.

YouTube Video