C#
ASP.NET
HTTP
cookie
ASP.NET_Core

ASP.NET Core 2.0 MVC で Cookie を利用する

開発・動作確認環境

  • 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の書き込み

基本形

CookieController.cs
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にアクセスしましたが)、ルート/に設定されるようです。

HttpResponseHeader(抜粋)
Set-Cookie: TestKey=TestValue; path=/

書き込まれたCookie情報をブラウザで確認します。Google Chromeでは「設定」-「詳細設定」-「コンテンツの設定」-「Cookie」-「すべての Cookie とサイトデータを表示」から、保存されているCookie情報を表示することができます。

クッキーの確認.png

次に、ブラウザから/home/indexにアクセスしてみます。そのときのリクエストヘッダをFiddlerを使って調べると、次のようにCookieヘッダが付加されていることが確認できます。Set-Cookieでパスがルート/に設定されていたので、同じドメイン(ここではlocalhost)の/以下のパス(例えばhome/index)にアクセスしたときもCookieが送信されています。
Set-Cookieに任意のパスを設定する方法は後述

HttpRequestHeader(抜粋)
Cookie: TestKey=TestValue

複数のCooikeの書き込み

次のように、2つのCookie情報を書き込んだときの動作を確認します。

CookieController.cs(抜粋)
public IActionResult Write() {
  this.HttpContext.Response.Cookies.Append("TestKey1", "TestValue1");
  this.HttpContext.Response.Cookies.Append("TestKey2", "TestValue2");
  return Ok();
}

レスポンスヘッダをFidllerで確認すると、次のような形式で2行のヘッダが追加されていることが確認できました(Set-Cookieヘッダは複数行になる)。

HttpResponseHeader(抜粋)
Set-Cookie: TestKey1=TestValue1; path=/
Set-Cookie: TestKey2=TestValue2; path=/

次に、上記Cookieがブラウザに保存されている状態でのリクエストヘッダを確認すると、次のようになりました。セミコロンで連結された情報が1行のヘッダとして付加されています。

HttpRequestHeader(抜粋)
Cookie: TestKey1=TestValue1; TestKey2=TestValue2

Cookieの読み込み

次のようにthis.HttpContext.Request.Cookies[KEY]で、リクエストメッセージ内のCookieの値を読み込むことができます。該当のキーが存在しないときはnullが戻ってきます。

CookieController.cs
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を直接読み込むことができます。

CookieController.cs(抜粋)
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」で保存しました。

CookieController.cs(抜粋)
public IActionResult Write() {
  this.HttpContext.Response.Cookies.Append("TestKey", "ほげほげ");
  return Ok();
}

/cookie/writeにアクセスしたときのレスポンスのSet-Cookieヘッダは次のようになりました。

HttpResponseHeader(抜粋)
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」で確認できます。

encode.png

つづいて、この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を追加しておきます。

CookieController.cs(抜粋)
public IActionResult Write() {
  var cOptions = new CookieOptions();
  this.HttpContext.Response.Cookies.Append(cKey,cValue,cOptions);
  return Ok();
}

レスポンスのSet-Cookieヘッダを確認するとsamesite=laxが追加されていることが分かります。

HttpResponseHeader(抜粋)
Set-Cookie: TestKey=TestValue; path=/; samesite=lax

samesite=laxについて日本語の情報が少なくあやふやですが、この属性があるとクロスドメインアクセスしたときに当該Cookieを送信しなくなるようです。ChromeでCookie情報を確認すると、次のように送信先が「あらゆる種類の接続」から「同サイトの接続のみ」に変わっていました。

送信先.png

もう少し実験します。次のようなファイルをデスクトップ上に作成してブラウザにドラッグ&ドロップします。アプリをデプロイしているドメイン(localhost)とは異なるドメイン(file:///C:/Users/xxxx/Desktop/test.html)に設定したJavaScriptから/cookie/readにアクセスするサンプルです。

test.html
<!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を送信するように設定しました。

CookieController.cs(抜粋)
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にアクセスしたときのレスポンスのヘッダは次のようになります。

HttpResponseHeader(抜粋)
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日を有効期限に設定しています。

CookieController.cs(抜粋)
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時間ずれています)。

HttpResponseHeader(抜粋)
Set-Cookie: TestKey=TestValue; expires=Sun, 10 Dec 2017 00:34:49 GMT; path=/cookie; samesite=lax

また、ブラウザでCookie情報を確認すると日本時間になっています(ユーザーに分かりやすいようにブラウザが変換して表示していると思われます)。

期限.png

CookieOptions.HttpOnly(JSからの参照制限)

JavaScriptからのCookie情報参照を制限するための設定です。CookieOptionsのExpiresプロパティをtrueにセットします。

CookieController.cs(抜粋)
    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属性がつきました。

HttpResponseHeader(抜粋)
Set-Cookie: TestKey=TestValue; path=/; samesite=lax; httponly

次のようなHTMLファイルを同じドメインのwwwrootの下に配置して動作を確認します。

test.html
<!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属性がつくと表示されなくなります。

jsc.png

ただし、httponly属性がついていても、JSからのAjaxの際にCookieがつかなくなるわけではないようです。

test.html
<!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()を使います。

CookieController.cs
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();
    }
  }
}

レスポンスのヘッダは次のようになります。保持期限を過去時刻にして上書きすることで実質的な削除を行なっています。

HttpResponseHeader(抜粋)
Set-Cookie: TestKey=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; samesite=lax

参考資料