TypeScriptにおける`Cookie`

TypeScriptにおける`Cookie`

この記事ではTypeScriptにおけるCookieについて説明します。

ブラウザとサーバーの両方で安全に、そして確実に Cookie を扱うための実践的なパターンを段階的に説明します。

YouTube Video

TypeScriptにおけるCookie

Cookie の基本概念

Cookie は小さな文字列(名前=値ペア)をクライアントに保存する仕組みで、HTTP ヘッダ Set-Cookie または document.cookie を通して作成されます。セキュリティ属性(HttpOnlySecureSameSite など)で挙動を制御できます。

ブラウザでの基本操作:document.cookie

以下はブラウザで Cookie を書き込む最小の例です。クッキーは 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');
  • このコードはクッキーを作成し、7日で失効します。ブラウザ側で簡単に使えますが、HttpOnly を付けられないため機密情報は避ける必要があります。

Cookie の読み取り(ブラウザ)

次の関数は、document.cookie から指定した名前の Cookie の値を取得するヘルパー関数です。document.cookie は「セミコロンと半角スペース(; )」で区切られた文字列として返されます。そのため、分割処理を行いながら目的の 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);
  • この関数は、Cookie 名と値の両方をデコードして安全に比較と取得を行います。Cookie 名の重複やエンコードされた特殊文字にも対応しており、シンプルながら堅牢な実装です。

安全な属性の理解と付加方法

Cookie にはいくつかの重要な属性があり、それぞれがセキュリティや動作範囲を制御します。

  • HttpOnly 属性は、JavaScript からその Cookie にアクセスできないようにするもので、XSS(クロスサイトスクリプティング)攻撃を防ぐのに役立ちます。ブラウザ側からは HttpOnly は付けられない点に注意してください。
  • Secure 属性は、Cookie が HTTPS 通信でのみ送信されるように制限し、盗聴や改ざんのリスクを減らします。
  • SameSite 属性は、Cookie がクロスサイトリクエスト時に送信されるかどうかを制御し、CSRF(クロスサイトリクエストフォージェリ)攻撃の防止に役立ちます。
  • Path 属性は Cookie が送信されるリクエストパスの範囲を、Domain 属性は Cookie が有効となるドメインを指定します。
  • Expires または Max-Age 属性を設定することで、Cookie の有効期限を制御することができます。

ブラウザで SameSite / Secure を付ける例(できる範囲)

HttpOnly はクライアント側では設定できません。以下はクライアント側で SameSiteSecure を付けるための例です。ただし Secure は 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 は安全で一般的な選択肢です。強い制限を望むなら SameSite=Strict を使いますが、外部からの正当な遷移が無効になる場合があります。

サーバー側での Set-Cookie(Node / TypeScript)

サーバー側では Set-Cookie ヘッダで HttpOnly を付けられるため、セッション管理はサーバ側で行うのが望ましいです。以下は Node の http モジュールを使った例です。

 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});
  • HttpOnly 属性をサーバー側で設定すると、JavaScript から Cookie にアクセスできなくなり、XSS(クロスサイトスクリプティング)攻撃による Cookie の窃取を防ぎやすくなります。また、Secure 属性を付与して常に HTTPS 通信で Cookie を送信するようにすることで、盗聴や改ざんを防ぎ、通信の安全性を高めることができます。

Cookie のシリアライズ/パース実装(サーバーで使えるヘルパー)

以下は 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"
  • このユーティリティはカスタムサーバーで Set-Cookie を作る際の基礎です。ライブラリを使えばより多くのエッジケースを処理できます。

署名(HMAC)で改ざんを防ぐパターン

Cookie に重要な値を直接入れるのは危険です。値をサーバー側で署名して改ざん検出を行う手法を紹介します。ここでは HMAC-SHA256 を使います。実際の運用では、サーバー側でのトークンの失効管理も必要です。

 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"
  • この方法で Cookie の値が改ざんされていないか検査できます。署名鍵はソースコード内に含めず、環境変数など安全な手段で管理する必要があります。

クッキーと CSRF 対策(一般パターン)

Cookie を使うセッションでは CSRF に注意が必要です。代表的な対策は次の通りです。

  • SameSiteLaxStrict に設定して不要なクロスサイト送信を防ぎます。クロスオリジン通信が必要な場合のみ SameSite=None; Secure を使用します。
  • フォームや API リクエストにはサーバが検証する CSRF トークンを使用します。トークンは HttpOnly Cookie ではなく、レスポンスボディやメタタグなどで提供し、必要時のみ JavaScript で読み出すようにします。
  • トークンを JavaScript から参照できる場合は、XSS 対策(CSP, 出力エスケープなど)を必ず併用します。
  • 重要操作やログイン時には再認証を要求し、セッション固定攻撃を防止します。

以下は CSRF トークンをヘッダに含めて送信する安全なクライアント例です。クライアントは、あらかじめサーバからレスポンスボディなどでトークンを受け取っていることを前提としています。

 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}
  • credentials: 'same-origin' を指定すると、同一オリジン内の Cookie だけが送信されます。サーバ側では、X-CSRF-Token ヘッダの値を検証し、トークンの一致を確認します。クロスサイト送信を行う必要がある場合は、CORS 設定を慎重に行います。

クロスサイト(CORS)と credentials の関係

クロスオリジン通信で Cookie を送受信する場合、クライアント側で credentials: 'include' を指定し、サーバ側で Access-Control-Allow-Credentials: true を設定する必要があります。ただし、この設定は信頼できるオリジンに限定し、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}
  • CORS と Cookie の組み合わせはセキュリティ上非常にデリケートです。許可オリジンはホワイトリストで厳密に管理し、不要なクロスサイト通信を避けます。また、SameSite=None; Secure の場合は HTTPS 通信を強制し、中間者攻撃を防ぎます。

例:Express(TypeScript)での安全なセッション Cookie 設定

実際の運用では Express や cookie ライブラリ、express-session などを利用します。以下は expresscookie-parser を使った簡易な例です。実践では secure を true にし、secret を環境変数で管理します。

 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);
  • cookieParsersecret 機能を利用すると、署名付き Cookie を手軽に扱えます。ただし、実際のアプリケーションでは、セキュリティやスケーラビリティの観点から、Cookie にデータを直接保存せず、専用のセッションストアの利用が適切です。

クッキーのプレフィックス __Host-__Secure-

ブラウザは特定のプレフィックスに特別ルールを課しています。

  • __Secure- プレフィックス Cookie 名が __Secure- で始まる場合、Secure 属性が必要です。
  • __Host- プレフィックス __Host- で始まる場合、Secure が必要で、Path=/(ルート)であり、Domain は指定できません。

これらを使うことでミス設定を減らし、安全性を高められます。

Cookie のベストプラクティス

Cookie を安全に取り扱うために、次の点を考慮できます。

  • 機密情報は Cookie に直接格納しないようにします。アクセストークンならサーバ側セッション ID を使えます。
  • セッション Cookie は HttpOnlySecureSameSite=Lax(または Strict)で設定します。
  • __Host-__Secure- などのプレフィックスを活用します。
  • 改ざん・盗聴対策に、署名(HMAC)や暗号化を検討します。
  • Secure を有効にし、HTTPS を必須にします。
  • 最小権限、短期間の有効期限を心がけます。
  • CSRF トークンを導入します。
  • 古いブラウザの場合など、ブラウザの SameSite の挙動差異に注意します。

クッキーに関するよくある誤解

クッキーに関しては、次のようなよくある誤解に注意が必要です。

  • HttpOnly を付ければ XSS の影響はなくなる」 HttpOnly は JavaScript からの Cookie 参照を防ぐものの、XSS によって任意のリクエストを送信される可能性は残ります。CSRF 対策トークンの検証、CSP(Content Security Policy)の導入、入力値のサニタイズなども併用する必要があります。
  • Secure はローカル開発では不要」 ローカル環境でも HTTPS を模擬して動作を確認することで、テストの精度が向上します。少なくともステージング環境や本番環境では HTTPS の利用が適切です。
  • 「長い有効期限は便利」 Cookie の有効期限を長く設定すると、万一盗まれた場合に悪用される期間が延びてしまいます。短めの有効期限を設定し、定期的な再認証やトークンのローテーションを取り入れることができます。

まとめ

Cookie は手軽に使える反面、扱い方を誤るとセキュリティ上の脆弱性を招く可能性があります。TypeScript を使って適切に管理するには、HttpOnlySecureSameSite などの属性を理解し、サーバー側での安全な設定を徹底することが重要です。機密情報を直接保存せず、署名や短期間の有効期限を組み合わせて運用することで、安全で信頼性の高いセッション管理を実現できます。

YouTubeチャンネルでは、Visual Studio Codeを用いて上記の記事を見ながら確認できます。 ぜひYouTubeチャンネルもご覧ください。

YouTube Video