JavaScriptでCookieを使ってブラウザへデータの保存・削除・読み込みを行う方法

JavaScriptでCookieを使ってブラウザへデータの保存・削除・読み込みを行う

Cookieは古くからWEBのデータ保存やセッション情報の管理に利用されてきました。

localStorageやsessionStorageはざまざななデータを保存できます。

これらと比較して、Cookieは1次元の文字列しか保存できないなど、制約がある一方メリットもあります。

また、Cookieを保存する方法は大きく分けて2つの種類がありますので、まずはその違いを理解しましょう。

Cookieを保存する2つの形式

HTTPレスポンスヘッダーでCookieを保存する方法

サーバー側がCookie(勝手に「サーバーCookie」と呼称します。)の発行元になるパターンです。本稿はJavaScriptでのCookieの扱いについてのページであるため、簡単な説明に留めます。

  • サーバーサイドで発行されたCookieを指す
  • 個人情報を扱う場合はhttponly属性を必ずつける。そうしないと セキュリティの脆弱性に繋がる。これはJavaScriptで内容を取得できる状態になってしまうため。
  • 容量は4kbまで
  • 有効期限を設定しなかった場合、ブラウザを閉じた時にデータが削除される
  • アクセスできるのはCookie発行元のドメインとサブドメイン(追加設定必要)
  • ブラウザにより異なるが1つのドメインが持つことができるCookie数は20程度
  • httpとhttpsプロトコルはそれぞれ同一のものとして扱われ、Cookieは別々に管理される。ただし、secureを付けた場合は別物になる
  • 保存できるのは文字列(シングルバイト)のみ
  • シングルバイト文字しか保存できないので、日本語などマルチバイト文字列はエンコード・デコードが必要

JavaScriptでCookieを保存する方法

JavaScriptがCookie(勝手に「JS Cookie」と呼称します。)の発行元になるパターンです。JavaScriptを使ってCookieを保存する方法自体はセキュリティ面で安全ですが、他の脆弱性をハッカーにつかれた時に、ユーザー情報がハッカーに漏れてしまう原因になりますので、極力個人情報の扱いには利用しないでください。

  • JavaScriptで発行されたCookieを指す
  • 個人情報を扱うべきではない。JavaScriptで設定されたCookieはJavaScript上で常に取得可能であり、ページ上にユーザーが任意のコードを挿入して動作させることができるバグがある場合にセキュリティの脆弱性に繋がる。
  • 容量は4kbまで
  • 有効期限を設定しなかった場合、ブラウザを閉じた時にデータが削除される
  • アクセスできるのはCookie発行元のドメインとサブドメイン(追加設定必要)
  • ブラウザにより異なるが1つのドメインが持つことができるCookie数は20程度
  • httpとhttpsプロトコルはそれぞれ同一のものとして扱われ、Cookieは別々に管理される。ただし、secureを付けた場合は別物になる
  • 保存できるのは文字列(シングルバイト)のみ
  • シングルバイト文字しか保存できないので、日本語などマルチバイト文字列はエンコード・デコードが必要

仕様

本稿はJavaScriptに関する記事なので、JS Cookieの扱い方について紹介していきます。(HTTPヘッダーと共通する部分は多数あります。)

プロパティー意味
document.cookieCookieを参照する文字列
JS Cookieの仕様

Cookieはそのプロパティーで1次元のデータしか持てません。なので、=や;などの特殊記号や日本語文字は「%82%A0」の様な形式にエンコードして記録しておき、読み出し時にはデコードする必要があります。

サンプルコード

基本形

まずは、最低限のコード量に抑えた基本型を紹介します。このサンプルコードは以下の事をおこなっています。

  1. テキストエリアの文字列をCookieにエンコードして保存する。
  2. ドメインに保存されているCookieを全て取得。Cookieは特定の値だけ取得する方法は用意していない。一度全取得した後にIDで探す方法はあるが、本稿では割愛する。(JavaScriptからのアクセスが許可されているものに限る)
  3. Cookieをデコードし読みやすい様にJSONオブジェクトに変換
  4. デコードされたデータをページ上に表示

セキュリティを担保するために、secureオプションを付与するのが普通です。紹介するコードには全てsecureをつけています。

Cookie は HTTPS 経由でのみ転送すべきです。

デフォルトでは、Cookie を http://example.com にセットした場合、それは https://example.com でも取得可能です。

これは、Cookie はドメインのみをチェックしており、プロトコルを区別していないためです。

このオプションを有効にすることで、HTTPプロトコルでの通信ではCookie情報を渡せなくなります。そのため、HTTPSプロトコル通信でのみCookieが扱われるため、機密情報漏洩のリスクが減ります。(必ず安全になるというわけではありません。)

オプション意味
secureHTTPSプロトコル通信でのみCookieの保存・取得・送信ができる様になる。
Cookieのsecureオプションに関して
Output
Cookieに保存されているデータ
Pug
textarea#targetTextarea.form-control
	table.table
		tr
			th Cookieに保存されているデータ
			td#targetText
	button#targetButton.brandia_btn.btn 保存
	#resultMessage.mt-4
JavaScript
//保存ボタン
const targetButton = document.getElementById( 'targetButton' );

//結果テキスト表示
const resultMessage = document.getElementById( 'resultMessage' );

//Cookieへの保存
targetButton.addEventListener('click', targetOnClick);

function targetOnClick() {
	
	//テキストエリア
	const targetTextarea = document.getElementById("targetTextarea");
	
	// 入力テキスト
	const inputText = targetTextarea.value;
	
	//cookieに保存
	document.cookie = `codelaboSample=${encodeURIComponent(inputText)}; secure`;
	
	//サイトに保存されているCookieを全て呼び出してJSONオブジェクトに変換
	const cookieObj = convertCookieToObject(document.cookie);
	
	//Cookie(JSONオブジェクトに変換済み)を出力
	targetText.textContent = JSON.stringify(cookieObj, null, '  ');
	
	resultMessage.textContent = '保存完了';
}

/**
 * クッキーをObjectに変換します。
 * @param cookies クッキー文字列
 * @return 連想配列
 */
function convertCookieToObject(cookies) {
  const cookieItems = cookies.split(';');

  const obj = {};
  cookieItems.forEach((item) => {
    // 「=」で分解
    var elem = item.split('=');
    // キーを取得
    const key = elem[0].trim();
    // バリューを取得
    const val = decodeURIComponent(elem[1]);
    // 保存
    obj[key] = val;
  });
  return obj;
}

有効期限を設定して保存

基本形のサンプルコードに「有効期限」を設定して保存します。有効期限の指定方法は以下の通り

オプション意味
max-age指定した秒数だけ有効期限になる
expires指定した日付までが有効期限になる
Cookieの有効期限の指定方法

max-ageとexpires両方あった場合はmax-ageが優先されます。今回はmax-ageを使って5秒だけ有効なCookieを実装しました。

よって「保存」ボタンを押したのち、5秒間は「読み込み」ボタンを押した時にデータが表示されます。

しかし、それ以降はCookieが削除され表示されなくなります。試してみてください。

Output
Cookieに保存されているデータ
Pug
textarea#targetTextarea2.form-control
	table.table
		tr
			th Cookieに保存されているデータ
			td#targetText2
	button#targetButton2.brandia_btn.btn 保存
	button#targetButton2_2.brandia_btn.btn 読み込み
	#resultMessage2.mt-4
JavaScript
//保存ボタン
const targetButton2 = document.getElementById( 'targetButton2' );
const targetButton2_2 = document.getElementById( 'targetButton2_2' );

//結果テキスト表示
const resultMessage2 = document.getElementById( 'resultMessage2' );

//Cookieへの保存
targetButton2.addEventListener('click', targetOnClick2);

function targetOnClick2() {
	
	//テキストエリア
	const targetTextarea2 = document.getElementById("targetTextarea2");
	
	// 入力テキスト
	const inputText2 = targetTextarea2.value;
	
	//cookieに保存(Cookie名はcodelaboSample2)
	const maxAgeSeconds = 5;  // 秒で指定
	document.cookie = `codelaboSample2=${encodeURIComponent(inputText2)}; max-age=${maxAgeSeconds}; secure`;
	//document.cookie = `codelaboSample2=${encodeURIComponent(inputText2)}`;
	
	//サイトに保存されているCookieを全て呼び出してJSONオブジェクトに変換
	const cookieObj2 = convertCookieToObject2(document.cookie);
	
	//Cookie(JSONオブジェクトに変換済み)を出力
	targetText2.textContent = JSON.stringify(cookieObj2, null, '  ');
	
	resultMessage2.textContent = '保存完了';
}

//Cookieの読み込み
targetButton2_2.addEventListener('click', targetOnClick2_2);
function targetOnClick2_2() {
	//保存データ出力先
	const targetText2 = document.getElementById("targetText2");
	//サイトに保存されているCookieを全て呼び出してJSONオブジェクトに変換
	const cookieObj2 = convertCookieToObject2(document.cookie);
	
	//Cookie(JSONオブジェクトに変換済み)を出力
	targetText2.textContent = JSON.stringify(cookieObj2, null, '  ');
	
	resultMessage2.textContent = '読み込み完了';
}

/**
 * クッキーをObjectに変換します。
 * @param cookies クッキー文字列
 * @return 連想配列
 */
function convertCookieToObject2(cookies) {
  const cookieItems = cookies.split(';');

  const obj = {};
  cookieItems.forEach((item) => {
    // 「=」で分解
    var elem = item.split('=');
    // キーを取得
    const key = elem[0].trim();
    // バリューを取得
    const val = decodeURIComponent(elem[1]);
    // 保存
    obj[key] = val;
  });
  return obj;
}

MaCookieを削除する

JavaScript上で特定のCookieを削除することができます。削除するには有効期限を0にするか、過去の日付を指定します。この時Cookieのデータは何でも構いません。

サンプルコードではdocument.cookie = codelaboSample3 = ; max-age=0;;とし、空文字を適用しています。

Output
Cookieに保存されているデータ
Pug
textarea#targetTextarea3.form-control
	table.table
		tr
			th Cookieに保存されているデータ
			td#targetText3
	button#targetButton3.brandia_btn.btn 保存
	button#targetButton3_2.brandia_btn.btn 削除
	#resultMessage3.mt-4
JavaScript
//保存ボタン
const targetButton3 = document.getElementById( 'targetButton3' );
const targetButton3_2 = document.getElementById( 'targetButton3_2' );

//結果テキスト表示
const resultMessage3 = document.getElementById( 'resultMessage3' );

//Cookieへの保存
targetButton3.addEventListener('click', targetOnClick3);

function targetOnClick3() {
	
	//テキストエリア
	const targetTextarea3 = document.getElementById("targetTextarea3");
	
	// 入力テキスト
	const inputText3 = targetTextarea3.value;
	
	//cookieに保存(Cookie名はcodelaboSample3)
	document.cookie = `codelaboSample3=${encodeURIComponent(inputText3)}; secure`;
	
	//サイトに保存されているCookieを全て呼び出してJSONオブジェクトに変換
	const cookieObj3 = convertCookieToObject3(document.cookie);
	
	//Cookie(JSONオブジェクトに変換済み)を出力
	targetText3.textContent = JSON.stringify(cookieObj3, null, '  ');
	
	resultMessage3.textContent = '保存完了';
}

//Cookieの読み込み
targetButton3_2.addEventListener('click', targetOnClick3_2);

function targetOnClick3_2() {
	
	//Cookieを削除
	document.cookie = `codelaboSample3 = ; max-age=0; secure`;
	
	//保存データ出力先
	const targetText3 = document.getElementById("targetText3");
	
	//サイトに保存されているCookieを全て呼び出してJSONオブジェクトに変換
	const cookieObj3 = convertCookieToObject3(document.cookie);
	
	//Cookie(JSONオブジェクトに変換済み)を出力
	targetText3.textContent = JSON.stringify(cookieObj3, null, '  ');
	
	resultMessage3.textContent = '削除完了';
}

/**
 * クッキーをObjectに変換します。
 * @param cookies クッキー文字列
 * @return 連想配列
 */
function convertCookieToObject3(cookies) {
  const cookieItems = cookies.split(';');

  const obj = {};
  cookieItems.forEach((item) => {
    // 「=」で分解
    var elem = item.split('=');
    // キーを取得
    const key = elem[0].trim();
    // バリューを取得
    const val = decodeURIComponent(elem[1]);
    // 保存
    obj[key] = val;
  });
  return obj;
}

domain・pathを指定する(おまけ)

domainpathを指定することで、Cookieが有効な範囲を制限することが可能です。

オプション意味
domain参照を許可するドメインを指定(サブドメインを許可する目的以外では使用しない)
pathサイト内のどのページで参照を許可するか絶対値で指定する。ワイルドカードが使用可能で初期値は全ページを許可する意味を示す「/」
Cookieのdomain pathオプションの仕様

domainを指定していない場合は、Cookie はそれを設定したドメインでのみアクセス可能です。これまでのサンプルコードではdomainを指定していないので、当サイト上でのみ有効でした。

そのため、Cookie が example.com で設定されている場合、 other.com では取得できません。

ではdomainを指定すればother.comで閲覧が可能になるのか?と聞かれれば、答えはNOです。

ブラウザ上のセキュリティ攻撃を防ぐ目的の仕様です。

では、どうしてこのオプションがあるのでしょうか?

答えは「デフォルトではサブドメインからの参照もブロックしてしまう」からです。

そのため、サブドメインからの参照を有効にする場合は、下記の様に明示的に指定してあげる必要があります。

JavaScript
document.cookie = "codelaboSample4="sampleText; domain=site.com"

また、pathを指定することで、サイト内のどのページでCookieの参照を許可するかを指定することもできます。

ただし、特段の理由がない場合は初期値の「/」で十分です。

JavaScript
document.cookie = "codelaboSample5="sampleText; path=/"; //あえて全ページを対象にすることを明示的に示したが、初期値が「/」なので必要はない

もし、mypath配下のページだけで参照させたい場合は下記の様に書きます。

JavaScript
document.cookie = "codelaboSample5="sampleText; path=/mypath/*"; 
インストラクター