開発・動作確認環境
- Visual Studio 2017 v 15.4.3
- ASP.NET Core Webアプリケーション MVC
- .NET Core 2.0
- Fiddler v5.0.20173.48897 for .NET 4.6.
- Google Chrome 62.0.3202.94
Cookieの書き込み
基本形
using Microsoft.AspNetCore.Mvc;
namespace AspDotNetCore2_Cookie.Controllers {
public class CookieController : Controller {
private const string cKey = "TestKey";
private string cValue = "TestValue";
public IActionResult Write() {
this.HttpContext.Response.Cookies.Append(cKey, cValue);
return Ok();
}
}
}
アクセスがあったときにブラウザにクッキーを書き込む(保存する・セットする)ためには、コントローラー上でthis.HttpContext.Response.Cookies.Append
(IResponseCookies.Append
)メソッド
を呼び出します。IResponseCookies.Append
の引数はstring型で与えます。
上記をビルドして/cookie/write
にアクセスしたときのレスポンスのヘッダをFiddlerを使って調べると、次のようなSet-Cookie
ヘッダが付加されていることが確認できます。Cookieは「Key=Value」の形式でセットされています。なお、pathはURLに関係なく(ここでは/cookie/write
にアクセスしましたが)、ルート/
に設定されるようです。
Set-Cookie: TestKey=TestValue; path=/
書き込まれたCookie情報をブラウザで確認します。Google Chromeでは「設定」-「詳細設定」-「コンテンツの設定」-「Cookie」-「すべての Cookie とサイトデータを表示」から、保存されているCookie情報を表示することができます。
次に、ブラウザから/home/index
にアクセスしてみます。そのときのリクエストヘッダをFiddlerを使って調べると、次のようにCookie
ヘッダが付加されていることが確認できます。Set-Cookie
でパスがルート/
に設定されていたので、同じドメイン(ここではlocalhost)の/
以下のパス(例えばhome/index
)にアクセスしたときもCookieが送信されています。
※Set-Cookie
に任意のパスを設定する方法は後述
Cookie: TestKey=TestValue
複数のCooikeの書き込み
次のように、2つのCookie情報を書き込んだときの動作を確認します。
public IActionResult Write() {
this.HttpContext.Response.Cookies.Append("TestKey1", "TestValue1");
this.HttpContext.Response.Cookies.Append("TestKey2", "TestValue2");
return Ok();
}
レスポンスヘッダをFidllerで確認すると、次のような形式で2行のヘッダが追加されていることが確認できました(Set-Cookie
ヘッダは複数行になる)。
Set-Cookie: TestKey1=TestValue1; path=/
Set-Cookie: TestKey2=TestValue2; path=/
次に、上記Cookieがブラウザに保存されている状態でのリクエストヘッダを確認すると、次のようになりました。セミコロンで連結された情報が1行のヘッダとして付加されています。
Cookie: TestKey1=TestValue1; TestKey2=TestValue2
Cookieの読み込み
次のようにthis.HttpContext.Request.Cookies[KEY]
で、リクエストメッセージ内のCookieの値を読み込むことができます。該当のキーが存在しないときはnullが戻ってきます。
using System;
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
namespace AspDotNetCore2_Cookie.Controllers {
public class CookieController : Controller {
private const string cKey = "TestKey";
private string cValue = "TestValue";
public IActionResult Read() {
string foo = this.HttpContext.Request.Cookies[cKey];
Debug.WriteLine(cKey + "=" + (foo?.ToString() ?? "[NULL]"));
return Ok();
}
public IActionResult Write() {
this.HttpContext.Response.Cookies.Append(cKey, cValue);
return Ok();
}
}
}
また、次のようにHttpContext.Request.Headers["Cookie"]
を使っても、リクエストに含まれるCookieを直接読み込むことができます。
public IActionResult Read() {
string foo = this.HttpContext.Request.Headers["Cookie"];
Debug.WriteLine((foo?.ToString() ?? "[NULL]"));
return Ok();
}
なお、この場合、複数のCookieがあると変数fooにはTestKey1=TestValue1; TestKey2=TestValue2
のように複数のCookie情報が連結された文字列が入るので、その後の処理が面倒になります。
Cookieに日本語を設定してみる
次のようにTestValueに日本語を入れてみます。CookieController.cs
は「Unicode(UTF-8シグネチャ付き) 65001」で保存しました。
public IActionResult Write() {
this.HttpContext.Response.Cookies.Append("TestKey", "ほげほげ");
return Ok();
}
/cookie/write
にアクセスしたときのレスポンスのSet-Cookie
ヘッダは次のようになりました。
Set-Cookie: TestKey=%E3%81%BB%E3%81%92%E3%81%BB%E3%81%92; path=/
ここで「%E3%81%BB%E3%81%92%E3%81%BB%E3%81%92」は、UTF-8の文字列「ほげほげ」をURLエンコードした文字列になります。Fiddlerの「Tools」-「TextWizard」で確認できます。
つづいて、このCookie情報をHttpContext.Request.Cookies["TestKey"]
で読み込んでみます。その結果、デバッグ出力は「ほげほげ」(文字化けなしの日本語)になりました。Response.Cookies'と'Request.Cookies
では、自動的にURLエンコード、デコードをしてくれるようです。
一方、HttpContext.Request.Headers["Cookie"]
を使って読み込むと、デコードされていない生の「%E3%81%BB%E3%....」という値が取得できます。
Cookiesの詳細オプション設定
レスポンスメッセージのなかのSet-Cookieヘッダに各種オプションを設定したいときは、Response.Cookies.Append
の第3引数にMicrosoft.AspNetCore.Http.CookieOptions
をセットします。
CookieOptions(デフォルト)
CookieOptions
のデフォルト設定から確認します。なお、CookieOptions
を利用する際は、名前空間using Microsoft.AspNetCore.Http
を追加しておきます。
public IActionResult Write() {
var cOptions = new CookieOptions();
this.HttpContext.Response.Cookies.Append(cKey,cValue,cOptions);
return Ok();
}
レスポンスのSet-Cookieヘッダを確認するとsamesite=lax
が追加されていることが分かります。
Set-Cookie: TestKey=TestValue; path=/; samesite=lax
samesite=lax
について日本語の情報が少なくあやふやですが、この属性があるとクロスドメインアクセスしたときに当該Cookieを送信しなくなるようです。ChromeでCookie情報を確認すると、次のように送信先が「あらゆる種類の接続」から「同サイトの接続のみ」に変わっていました。
もう少し実験します。次のようなファイルをデスクトップ上に作成してブラウザにドラッグ&ドロップします。アプリをデプロイしているドメイン(localhost)とは異なるドメイン(file:///C:/Users/xxxx/Desktop/test.html)に設定したJavaScriptから/cookie/read
にアクセスするサンプルです。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<script>
var req = new XMLHttpRequest();
req.open('GET', 'http://localhost:8080/cookie/read');
req.withCredentials = true;
req.send(null);
</script>
</body>
</html>
Fiddlerで確認すると、samesite=lax
が設定されているときにはJSからのリクエストにCookieが含まれていませんでした。一方、samesite=lax
が設定されていないときには、リクエストにCookieが含まれていました。なお、req.withCredentials=true
がないと、どちらの場合でもCookieを送信しないようです。
CookieOptions.Path(送信先の設定)
同一ドメイン内でのCookie送信先を制限するためには、次のようにPathプロパティを設定します。Pathプロパティはstring型でセットします。ここでは/cookie
配下にアクセスするときのみ、cookieを送信するように設定しました。
private const string cKey = "TestKey";
private string cValue = "TestValue";
public IActionResult Write() {
var cOptions = new CookieOptions() {
Path="/cookie"
};
this.HttpContext.Response.Cookies.Append(cKey,cValue,cOptions);
return Ok();
}
上記/cookie/write
にアクセスしたときのレスポンスのヘッダは次のようになります。
Set-Cookie: TestKey=TestValue; path=/cookie; samesite=lax
この状態でのcookie送信の有無は次のようになります。
-
/cookie/read
→ 送信される -
/cookie
→ 送信される -
/
→ 送信されない -
/home/index
→ 送信されない -
/Cookie/read
→ 送信されない ※コントローラー名を大文字で指定
CookieOptions.Expires(有効期限の設定)
CookieOptionsを与えずにCookies.Append
した場合や、CookieOptionsのデフォルト値を与えてCookies.Append
した場合、そのCookieの有効期限は「ブラウザセッションの終了時」つまり、そのブラウザを閉じるまでに設定されました。Expiresプロパティを与えると、その有効期限を任意に設定することができます。ExpiresプロパティはDateTimeOffset型でセットします(DateTime型ではない)。
ここでは、アクセス時刻+2日を有効期限に設定しています。
private const string cKey = "TestKey";
private string cValue = "TestValue";
public IActionResult Write() {
var cOptions = new CookieOptions() {
Path="/cookie",
Expires = new DateTimeOffset(DateTime.Now.AddDays(2))
};
this.HttpContext.Response.Cookies.Append(cKey,cValue,cOptions);
return Ok();
上記/cookie/write
に、2017/12/08 Fri 09:34:49 にアクセスしたときのレスポンスのヘッダは次のようになります。Cookieの有効期限は、GMTグリニッジ標準時でセットされるようです(日本時間と9時間ずれています)。
Set-Cookie: TestKey=TestValue; expires=Sun, 10 Dec 2017 00:34:49 GMT; path=/cookie; samesite=lax
また、ブラウザでCookie情報を確認すると日本時間になっています(ユーザーに分かりやすいようにブラウザが変換して表示していると思われます)。
CookieOptions.HttpOnly(JSからの参照制限)
JavaScriptからのCookie情報参照を制限するための設定です。CookieOptionsのExpiresプロパティをtrue
にセットします。
private const string cKey = "TestKey";
private string cValue = "TestValue";
public IActionResult Write() {
var cOptions = new CookieOptions() {
HttpOnly = true
};
this.HttpContext.Response.Cookies.Append(cKey,cValue,cOptions);
return Ok();
}
レスポンスを確認するとSet-Cookie
にhttponly属性がつきました。
Set-Cookie: TestKey=TestValue; path=/; samesite=lax; httponly
次のようなHTMLファイルを同じドメインのwwwrootの下に配置して動作を確認します。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<script>
var cookies = document.cookie;
console.log(cookies);
</script>
</body>
</html>
httponly属性がついていないときはコンソールログに次のようにcookie情報が表示されますが、httponly属性がつくと表示されなくなります。
ただし、httponly属性がついていても、JSからのAjaxの際にCookieがつかなくなるわけではないようです。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<script>
var cookies = document.cookie;
console.log(cookies);
var req = new XMLHttpRequest();
req.open('GET', '/cookie/read');
req.withCredentials = true;
req.send(null); // この通信ではCookieが送信される
</script>
</body>
</html>
Cookieの削除
Cookieの削除にはHttpContext.Response.Cookies.Delete()
を使います。
using System;
using Microsoft.AspNetCore.Mvc;
namespace AspDotNetCore2_Cookie.Controllers {
public class CookieController : Controller {
private const string cKey = "TestKey";
public IActionResult Delete() {
this.HttpContext.Response.Cookies.Delete(cKey);
return Ok();
}
}
}
レスポンスのヘッダは次のようになります。保持期限を過去時刻にして上書きすることで実質的な削除を行なっています。
Set-Cookie: TestKey=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; samesite=lax
参考資料
- Microsoft.AspNetCore.Http.CookieOptions のリファレンス