現象
CookieOptions.HttpOnly = false
が適用されず、ブラウザにhttponly属性が付与されたset-cookieが返る為、JavaScriptからcookieが取得できない。
環境
ASP.NET Core 5
そもそもの目的
- AntiForgery用のリクエスト・トークンを非同期通信(ajax)の際にも更新したい。
- その為に、XSRF-TOKENをcookieとして返却し、それをJavaScript側で読み取ってリクエスト・ヘッダー('X-XSRF-TOKEN')に追加する仕組みを作った。これはAngularやaxiosがサポートしているものと同じ仕組みである。
- しかし、なぜかJavaScript側でcookieの取得に失敗する。
- ネットワーク状態を確認すると、XSRF-TOKENにhttponly属性が付加されていた。
- サーバ側では、
CookieOptions.HttpOnly = false
は指定している。 - 何が起きているのか?(この記事の主旨)
原因
サーバ側で以下のように指定されていた為、CookieOptionsの指定に関係なく、cookieの戻り値が強制的にhttponlyになっていた。
services.Configure<CookiePolicyOptions>(options => options.HttpOnly = HttpOnlyPolicy.Always)
主なソースコード
public class AntiforgeryCookieResultFilter : ResultFilterAttribute
{
/// <summary>Antiforgeryリクエストトークン用のcookie名</summary>
public static string RequestTokenCookieName => "XSRF-TOKEN"; // AngularのXSRFサポート機能に準拠
/// <summary>Antiforgeryリクエストトークン用のリクエストヘッダ名</summary>
public static string RequestTokenHeaderName => "X-XSRF-TOKEN"; // AngularのXSRFサポート機能に準拠
private IAntiforgery antiforgery;
public AntiforgeryCookieResultFilter(IAntiforgery antiforgery)
{
this.antiforgery = antiforgery;
}
public override void OnResultExecuting(ResultExecutingContext context)
{
if (context.Result is ViewResult || context.Result is JsonResult)
{
// Antiforgery用のクッキートークンを生成してクッキーに保存する
var tokens = antiforgery.GetAndStoreTokens(context.HttpContext);
// Antiforgery用のリクエストトークンをクッキーに保存する
context.HttpContext.Response.Cookies.Append(RequestTokenCookieName, tokens.RequestToken, new CookieOptions() {
HttpOnly = false,
Secure = true,
SameSite = SameSiteMode.Strict
});
}
}
}
// (略)
services.AddMvc(options => {
// 既存の処理
...
// ここでAntiforgeryCookieResultFilterをグローバル登録する
options.Filters.Add<Filters.AntiforgeryCookieResultFilter>();
}
// AntiForgeryの設定
services.AddAntiforgery(options =>
{
// AntiForgeryリクエスト・ヘッダの指定
options.HeaderName = Filters.AntiforgeryCookieResultFilter.RequestTokenHeaderName;
});
原因となった箇所
// CookiePolicy Middleware の設定
services
.Configure<CookiePolicyOptions>(x => {
// 常にHttpOnlyをtrueにする
x.HttpOnly = HttpOnlyPolicy.Always; // ★この箇所が原因で、全てのCookie指定がHttpOnly=trueで上書きされていた
});
上記を以下のように変更することで、HttpOnlyの個別指定が有効になった。
// CookiePolicy Middleware の設定
services
.Configure<CookiePolicyOptions>(x => {
// HttpOnlyのポリシー指定なし
x.HttpOnly = HttpOnlyPolicy.None;
});
もちろん、この変更を行う前に「そもそもなぜHttpOnlyPolicy.Alwaysが指定されていたのか」をよく調査する必要がある。
この変更によって、それまでそのシステムの他の部分で設定していたクッキーについてもこのクッキーポリシーに従うようになる為、HttpOnlyの指定を省略していた箇所があるのであれば、適切にHttpOnly = true
を指定する必要があるかを判断し、個別に指定する必要がある。
補足
上記のクッキーポリシーをHttpOnlyPolicy.Always
にしつつ、任意の箇所だけ個別に HttpOnly = false
にする方法があるのであれば知りたいが、いろいろ調べたり試したりしたところではどのようにしても HttpOnlyPolicy.Always
が優先されてしまい、false
で上書きすることはできなかった。
ネットを見ると、「HttpOnly=falseにしても、勝手にhttponly属性がついてしまう」と嘆いている投稿が散見されるので、そういう人たちの一助になれば幸いである。