クッキーは、ブラウザに直接保存される小さなデータ文字列です。それらはHTTPプロトコルの 一部であり、RFC 6265仕様で定義されています。
クッキーは通常、WebサーバーによってレスポンスのSet-Cookie
HTTPヘッダーを使用して設定されます。その後、ブラウザはそれをCookie
HTTPヘッダーを使用して、同じドメインへの(ほぼ)すべてのリクエストに自動的に追加します。
最も広く普及しているユースケースの1つは、認証です。
- サインイン時に、サーバーはレスポンスの
Set-Cookie
HTTPヘッダーを使用して、一意の「セッション識別子」を含むクッキーを設定します。 - 次回、同じドメインにリクエストが送信されると、ブラウザは
Cookie
HTTPヘッダーを使用してクッキーをネットワーク経由で送信します。 - そのため、サーバーは誰がリクエストを行ったかを知ることができます。
document.cookie
プロパティを使用して、ブラウザからクッキーにアクセスすることもできます。
クッキーとその属性については、多くの難しい点があります。この章では、それらを詳細に説明します。
document.cookieからの読み取り
あなたのブラウザはこのサイトからクッキーを保存していますか?見てみましょう。
// At javascript.info, we use Google Analytics for statistics,
// so there should be some cookies
alert( document.cookie ); // cookie1=value1; cookie2=value2;...
document.cookie
の値は、;
で区切られたname=value
のペアで構成されています。それぞれが個別のクッキーです。
特定のクッキーを見つけるには、;
でdocument.cookie
を分割し、正しい名前を見つけます。正規表現または配列関数を使用してそれを行うことができます。
これは読者への練習問題として残しておきます。また、章の最後には、クッキーを操作するためのヘルパー関数が掲載されています。
document.cookieへの書き込み
document.cookie
に書き込むことができます。しかし、それはデータプロパティではなく、アクセサ(ゲッター/セッター)です。それへの代入は特別に処理されます。
document.cookie
への書き込み操作は、そこに記載されているクッキーのみを更新し、他のクッキーには影響しません。
たとえば、この呼び出しは、名前がuser
で値がJohn
のクッキーを設定します。
document.cookie = "user=John"; // update only cookie named 'user'
alert(document.cookie); // show all cookies
実行すると、複数のクッキーが表示される可能性があります。これは、document.cookie=
操作がすべてのクッキーを上書きしないためです。言及されたクッキーuser
のみを設定します。
技術的には、名前と値には任意の文字を含めることができます。有効なフォーマットを維持するには、組み込みのencodeURIComponent
関数を使用してエスケープする必要があります。
// special characters (spaces) need encoding
let name = "my name";
let value = "John Smith"
// encodes the cookie as my%20name=John%20Smith
document.cookie = encodeURIComponent(name) + '=' + encodeURIComponent(value);
alert(document.cookie); // ...; my%20name=John%20Smith
いくつかの制限事項があります。
document.cookie
を使用して一度に1つのクッキーのみを設定/更新できます。encodeURIComponent
後のname=value
ペアは4KBを超えてはなりません。そのため、クッキーには大量のデータを保存できません。- ドメインごとのクッキーの総数は約20個に制限されており、正確な制限はブラウザによって異なります。
クッキーにはいくつかの属性があり、その多くは重要であり、設定する必要があります。
属性はkey=value
の後にリストされ、次のように;
で区切られています。
document.cookie = "user=John; path=/; expires=Tue, 19 Jan 2038 03:14:07 GMT"
ドメイン
domain=site.com
ドメインは、クッキーにアクセスできる場所を定義します。しかし実際には、制限があります。任意のドメインを設定することはできません。
別の2レベルドメイン(例:other.com
)から、site.com
で設定されたクッキーにアクセスできるようにすることはできません。
これは、1つのサイトでのみ使用できる機密データをクッキーに保存できるようにするための安全上の制限です。
デフォルトでは、クッキーはそれを設定したドメインでのみアクセスできます。
デフォルトでは、クッキーはforum.site.com
などのサブドメインと共有されません。
// if we set a cookie at site.com website...
document.cookie = "user=John"
// ...we won't see it at forum.site.com
alert(document.cookie); // no user
…しかし、これは変更できます。forum.site.com
などのサブドメインがsite.com
で設定されたクッキーを取得できるようにしたい場合は、可能です。
そのためには、site.com
でクッキーを設定する際に、ルートドメインにdomain
属性を明示的に設定する必要があります:domain=site.com
。そうすると、すべてのサブドメインがそのクッキーを参照できます。
例:
// at site.com
// make the cookie accessible on any subdomain *.site.com:
document.cookie = "user=John; domain=site.com"
// later
// at forum.site.com
alert(document.cookie); // has cookie user=John
歴史的に、domain=.site.com
(site.com
の前にドットがある)は同じように機能し、サブドメインからクッキーへのアクセスを許可していました。ドメイン名の先頭のドットは現在無視されますが、一部のブラウザは、そのようなドットを含むクッキーの設定を拒否することがあります。
要約すると、domain
属性により、サブドメインでクッキーにアクセスできるようにすることができます。
パス
path=/mypath
URLパスのプレフィックスは絶対パスでなければなりません。これにより、そのパス以下のページでクッキーにアクセスできるようになります。デフォルトでは、現在のパスです。
path=/admin
でクッキーが設定されている場合、/admin
および/admin/something
のページでは表示されますが、/home
、/home/admin
、または/
では表示されません。
通常、すべてのWebサイトページからクッキーにアクセスできるようにするには、path
をルートに設定する必要があります:path=/
。この属性が設定されていない場合、デフォルトはこの方法を使用して計算されます。
expires、max-age
デフォルトでは、クッキーにこれらの属性のいずれかが含まれていない場合、ブラウザ/タブを閉じると消えます。このようなクッキーは「セッションクッキー」と呼ばれます。
ブラウザの閉じてもクッキーを保持するには、expires
属性またはmax-age
属性を設定できます。両方が設定されている場合、max-Age
が優先されます。
expires=Tue, 19 Jan 2038 03:14:07 GMT
クッキーの有効期限は、ブラウザが自動的に削除する時刻を定義します(ブラウザのタイムゾーンに従います)。
日付は、GMTタイムゾーンでこのフォーマットで正確に指定する必要があります。date.toUTCString
を使用して取得できます。たとえば、クッキーを1日後に期限切れに設定できます。
// +1 day from now
let date = new Date(Date.now() + 86400e3);
date = date.toUTCString();
document.cookie = "user=John; expires=" + date;
expires
を過去の日付に設定すると、クッキーは削除されます。
max-age=3600
これはexpires
の代替であり、現在の時点からの秒数でクッキーの有効期限を指定します。
0または負の値に設定すると、クッキーは削除されます。
// cookie will die in +1 hour from now
document.cookie = "user=John; max-age=3600";
// delete cookie (let it expire right now)
document.cookie = "user=John; max-age=0";
secure
secure
クッキーはHTTPS経由でのみ転送する必要があります。
デフォルトでは、http://site.com
でクッキーを設定すると、https://site.com
にも表示され、その逆も同様です。
つまり、クッキーはドメインベースであり、プロトコルを区別しません。
この属性を使用すると、https://site.com
によってクッキーが設定されている場合、同じサイトにHTTP(http://site.com
)でアクセスしたときには表示されません。したがって、暗号化されていないHTTP経由で送信されるべきではない機密情報が含まれているクッキーの場合、secure
フラグが適切です。
// assuming we're on https:// now
// set the cookie to be secure (only accessible over HTTPS)
document.cookie = "user=John; secure";
samesite
これは、別のセキュリティ属性samesite
です。これは、いわゆるXSRF(クロスサイトリクエストフォージェリ)攻撃から保護するために設計されています。
それがどのように機能し、いつ役立つのかを理解するために、XSRF攻撃を見てみましょう。
XSRF攻撃
bank.com
サイトにログインしているとします。つまり、そのサイトから認証クッキーがあります。ブラウザは、ユーザーを認識し、すべての機密性の高い金融操作を実行するために、すべてのリクエストでbank.com
に送信します。
さて、別のウィンドウでWebを閲覧しているときに、誤って別のサイトevil.com
にアクセスしたとします。そのサイトには、ハッカーのアカウントへの取引を開始するフィールドを含む、bank.com
に<form action="https://bank.com/pay">
フォームを送信するJavaScriptコードがあります。
フォームがevil.com
から送信された場合でも、bank.com
サイトにアクセスするたびに、ブラウザはクッキーを送信します。したがって、銀行はユーザーを認識し、支払いを処理します。
これは、いわゆる「クロスサイトリクエストフォージェリ」(略してXSRF)攻撃です。
もちろん、実際の銀行はそれらから保護されています。bank.com
によって生成されたすべてのフォームには、悪意のあるページが生成したり、リモートページから抽出したりできない特別なフィールド、いわゆる「XSRF保護トークン」があります。悪意のあるページはそこにフォームを送信できますが、データを取り戻すことはできません。bank.com
サイトは、受信するすべてのフォームでこのようなトークンをチェックします。
しかし、このような保護を実装するには時間がかかります。すべてのフォームに必要なトークンフィールドがあることを確認し、すべてのリクエストもチェックする必要があります。
クッキーのsamesite属性を使用する
クッキーのsamesite
属性は、(理論的には)「xsrf保護トークン」を必要としない、そのような攻撃から保護する別の方法を提供します。
2つの可能な値があります。
samesite=strict
samesite=strict
を持つクッキーは、ユーザーが同じサイトの外部から来た場合、決して送信されません。
言い換えれば、ユーザーがメールからリンクをクリックしたり、evil.com
からフォームを送信したり、別のドメインから発信される操作を行ったりする場合、クッキーは送信されません。
認証クッキーにsamesite=strict
属性がある場合、evil.com
からの送信はクッキーなしで行われるため、XSRF攻撃が成功する可能性はありません。そのため、bank.com
はユーザーを認識せず、支払いを処理しません。
保護は非常に信頼性が高いです。bank.com
から来た操作のみがsamesite=strict
クッキーを送信します(例:bank.com
の別のページからのフォーム送信)。
ただし、小さな不便があります。
ユーザーがメモなどからbank.com
への正当なリンクをクリックすると、bank.com
がユーザーを認識しないことに驚かれるでしょう。実際、この場合、samesite=strict
クッキーは送信されません。
これは、2つのCookieを使用することで回避できます。「一般的な認識」用Cookie(「こんにちは、John」と表示するだけのもの)と、samesite=strict
を付けたデータ変更操作用Cookieです。サイト外部からアクセスしたユーザーには歓迎メッセージが表示されますが、支払いは銀行のウェブサイトから開始する必要があります。そうすることで、2つ目のCookieが送信されます。
samesite=lax
(値なしのsamesite
と同じ)
XSRF攻撃からも保護し、ユーザーエクスペリエンスを損なわない、より緩やかなアプローチです。
laxモードは、strict
モードと同様に、サイト外部からのアクセスではCookieの送信を禁止しますが、例外が追加されています。
samesite=lax
Cookieは、次の2つの条件が両方とも真である場合に送信されます。
-
HTTPメソッドが「安全」である(例:GETだが、POSTではない)。
安全なHTTPメソッドの完全なリストは、RFC7231仕様に記載されています。これらはデータの読み取りに使用すべきメソッドであり、書き込みには使用すべきではありません。データ変更操作を実行してはなりません。リンクをクリックする操作は常にGET(安全なメソッド)です。
-
操作がトップレベルのナビゲーションを実行する(ブラウザのアドレスバーのURLを変更する)。
通常は真ですが、
<iframe>
内でナビゲーションが行われる場合は、トップレベルではありません。さらに、ネットワークリクエスト用のJavaScriptメソッドは、ナビゲーションを実行しません。
つまり、samesite=lax
は、最も一般的な「URLに移動する」操作でCookieを許可します。たとえば、これらの条件を満たすメモからウェブサイトのリンクを開く場合などです。
しかし、別のサイトからのネットワークリクエストやフォームの送信など、より複雑な操作では、Cookieは失われます。
それで問題ない場合は、samesite=lax
を追加しても、ユーザーエクスペリエンスが損なわれることはなく、保護機能が追加されます。
全体的に見て、samesite
は優れた属性です。
欠点があります。
samesite
は、2017年頃までの非常に古いブラウザでは無視されます(サポートされていません)。
そのため、保護のためにsamesite
のみに依存する場合、古いブラウザは脆弱になります。
しかし、XSRFトークンなどの他の保護策とsamesite
を併用して防御層を追加することで、将来、古いブラウザが消滅すれば、XSRFトークンを削除できるようになるでしょう。
httpOnly
この属性はJavaScriptとは関係ありませんが、完全性のために言及する必要があります。
ウェブサーバーは、Set-Cookie
ヘッダーを使用してCookieを設定します。また、httpOnly
属性を設定することもできます。
この属性は、JavaScriptによるCookieへのアクセスを禁止します。document.cookie
を使用して、そのようなCookieを参照したり操作したりすることはできません。
これは、ハッカーが独自のJavaScriptコードをページに挿入し、ユーザーがそのページにアクセスするのを待つ特定の攻撃から保護するための予防措置として使用されます。そもそもそのようなことは起こるべきではありません。ハッカーは当社のサイトにコードを挿入することはできませんが、それを可能にするバグが存在する可能性があります。
通常、そのようなことが発生し、ユーザーがハッカーのJavaScriptコードを含むウェブページにアクセスすると、そのコードが実行され、認証情報を含むユーザーCookieにアクセスするdocument.cookie
へのアクセス権が得られます。これは危険です。
しかし、CookieがhttpOnly
の場合、document.cookie
はそのCookieを参照しないため、保護されます。
付録:Cookie関数
ここでは、document.cookie
を手動で変更するよりも便利な、Cookieを操作するための小さな関数セットを示します。
多くのCookieライブラリが存在するため、これらはデモ目的です。ただし、完全に機能します。
getCookie(name)
Cookieにアクセスする最も簡単な方法は、正規表現を使用することです。
getCookie(name)
関数は、指定されたname
のCookieを返します。
// returns the cookie with the given name,
// or undefined if not found
function getCookie(name) {
let matches = document.cookie.match(new RegExp(
"(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + "=([^;]*)"
));
return matches ? decodeURIComponent(matches[1]) : undefined;
}
ここでnew RegExp
は動的に生成され、; name=
に一致します。
Cookieの値はエンコードされているため、getCookie
は組み込みのdecodeURIComponent
関数を使用してデコードすることに注意してください。
setCookie(name, value, attributes)
デフォルトでpath=/
を使用して(他のデフォルトを追加するように変更できます)、Cookieのname
を指定されたvalue
に設定します。
function setCookie(name, value, attributes = {}) {
attributes = {
path: '/',
// add other defaults here if necessary
...attributes
};
if (attributes.expires instanceof Date) {
attributes.expires = attributes.expires.toUTCString();
}
let updatedCookie = encodeURIComponent(name) + "=" + encodeURIComponent(value);
for (let attributeKey in attributes) {
updatedCookie += "; " + attributeKey;
let attributeValue = attributes[attributeKey];
if (attributeValue !== true) {
updatedCookie += "=" + attributeValue;
}
}
document.cookie = updatedCookie;
}
// Example of use:
setCookie('user', 'John', {secure: true, 'max-age': 3600});
deleteCookie(name)
Cookieを削除するには、有効期限を負の値で呼び出すことができます。
function deleteCookie(name) {
setCookie(name, "", {
'max-age': -1
})
}
注意:Cookieを更新または削除する際には、設定時とまったく同じパスとドメイン属性を使用する必要があります。
まとめ:cookie.js.
付録:サードパーティCookie
Cookieは、ユーザーが訪問しているページ以外のドメインによって配置された場合、「サードパーティCookie」と呼ばれます。
たとえば
-
site.com
のページが別のサイトからバナーを読み込む場合:<img src="https://ads.com/banner.png">
。 -
バナーと共に、
ads.com
のリモートサーバーは、id=1234
のようなCookieを含むSet-Cookie
ヘッダーを設定する場合があります。そのようなCookieはads.com
ドメインから発信され、ads.com
でのみ表示されます。 -
次に
ads.com
にアクセスすると、リモートサーバーはid
Cookieを取得し、ユーザーを認識します。 -
さらに重要なのは、ユーザーが
site.com
から別のサイトother.com
に移動した場合も、バナーがある場合、ads.com
はCookieを取得します。これはads.com
に属しているため、訪問者を認識し、サイト間を移動する際に追跡します。
サードパーティCookieは、その性質上、従来から追跡と広告サービスに使用されてきました。これらは発信元ドメインにバインドされているため、ads.com
は、すべてのサイトがアクセスしている場合、異なるサイト間で同じユーザーを追跡できます。
当然のことながら、追跡されたくない人もいるため、ブラウザではそのようなCookieを無効にすることができます。
また、一部の最新のブラウザでは、そのようなCookieに対して特別なポリシーが採用されています。
- Safariは、サードパーティCookieをまったく許可しません。
- Firefoxには、サードパーティCookieをブロックするサードパーティドメインの「ブラックリスト」が付属しています。
<script src="https://google-analytics.com/analytics.js">
のように、サードパーティドメインからスクリプトを読み込み、そのスクリプトがdocument.cookie
を使用してCookieを設定する場合、そのようなCookieはサードパーティCookieではありません。
スクリプトがCookieを設定する場合、スクリプトの出所がどこであっても、Cookieは現在のウェブページのドメインに属します。
付録:GDPR
このトピックはJavaScriptとはまったく関係ありませんが、Cookieを設定する際に覚えておくべき事項です。
ヨーロッパにはGDPRと呼ばれる法律があり、ウェブサイトがユーザーのプライバシーを尊重するためのルールを施行しています。これらのルールの1つは、ユーザーからの追跡Cookieに対する明示的な許可を求めることです。
これは、追跡/識別/認証Cookieに関することだけであることに注意してください。
そのため、ユーザーを追跡も識別もしない情報を保存するだけのCookieを設定する場合、自由に設定できます。
しかし、認証セッションまたは追跡IDを含むCookieを設定する場合、ユーザーがそれを許可する必要があります。
ウェブサイトは一般的に、GDPRへの準拠について2つの方法があります。ウェブ上で両方を見たことがあるでしょう。
-
ウェブサイトが認証済みユーザーのみに追跡Cookieを設定する場合。
そのためには、登録フォームに「プライバシーポリシーに同意する」(Cookieの使用方法について説明されている)チェックボックスを含める必要があります。ユーザーがそれをチェックすると、ウェブサイトは認証Cookieを設定できます。
-
ウェブサイトがすべてのユーザーに対して追跡Cookieを設定する場合。
合法的にこれを行うには、ウェブサイトは新規ユーザーに対してモーダルの「スプラッシュスクリーン」を表示し、Cookieに同意するよう求めます。その後、ウェブサイトはCookieを設定し、ユーザーにコンテンツを表示できます。ただし、新規訪問者にとっては煩わしい場合があります。コンテンツの代わりにそのような「必須クリック」モーダルスプラッシュスクリーンを見たい人はいません。しかし、GDPRは明示的な同意を要求しています。
GDPRはCookieだけでなく、他のプライバシー関連の問題にも関係していますが、それは私たちの範囲を超えています。
概要
document.cookie
はCookieへのアクセスを提供します。
- 書き込み操作は、その中に記載されているCookieのみを変更します。
- 名前/値はエンコードする必要があります。
- 1つのCookieのサイズは4KBを超えることはできません。ドメインで許可されるCookieの数は約20個(ブラウザによって異なります)。
Cookie属性
path=/
は、デフォルトでは現在のパスであり、そのパスでのみCookieが表示されます。domain=site.com
は、デフォルトではCookieは現在のドメインでのみ表示されます。ドメインを明示的に設定すると、Cookieはサブドメインでも表示されるようになります。expires
またはmax-age
は、Cookieの有効期限を設定します。これらがなければ、Cookieはブラウザが閉じられると消えます。secure
は、CookieをHTTPS専用にします。samesite
は、ブラウザがサイト外部からのリクエストでCookieを送信することを禁止します。これは、XSRF攻撃を防ぐのに役立ちます。
その他
- ブラウザはサードパーティCookieを禁止する場合があります。たとえば、Safariはデフォルトでそうしています。Chromeにもこれを実装するための作業が進められています。
- EU市民のために追跡Cookieを設定する場合は、GDPRで許可を求める必要があります。
コメント
<code>
タグを使用し、複数行の場合は<pre>
タグで囲み、10行を超える場合はサンドボックス(plnkr、jsbin、codepen…)を使用してください。