私はつい最近まで、JavaScriptが送ったHTTPリクエスト(AjaxやFetch)に対するレスポンスでは、クッキーをセットできないと思い込んでいました。そのため、それ相当のことをするには、レスポンスでクッキーに入れたい値を受け取り、document.cookie = newCookie;のようにJavaScriptでセットしないといけないものだと思い込んでいました。そのためHttpOnlyディレクティブを使うことができないとも。

これは間違いで、JavaScriptからのHTTPリクエストに対するレスポンスがSet-Cookieヘッダーを持っていれば、自動的にブラウザにそれが保存されます。喜ばしいことに、このときHttpOnlyディレクティブを有効にすることもできます。

ただしJavaScriptでCookieをセットするときに、HttpOnlyディレクティブを使えないのは真です。HttpOnlyディレクティブつきのCookieはJavaScriptから読めないだけではなく書くこともできません。

クッキーの付与と表示を行うPythonスクリプトを用意して確かめる

これをcookie.pyとします。python3 cookie.pyで起動できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
from typing import (
    List,
    TYPE_CHECKING,
)
import wsgiref.simple_server
if TYPE_CHECKING:
    import wsgiref.types


def return_html(start_response: 'wsgiref.types.StartResponse') -> List[bytes]:
    start_response('200 OK', [('Content-type', 'text/html')])
    return [
        b"""<html>
    <head>
        <script>
            fetch('/', { method: 'POST', credentials: 'same-origin' });
        </script>
    </head>
</html>
""",
    ]


def give_cookie(start_response: 'wsgiref.types.StartResponse') -> List[bytes]:
    start_response('200 OK', [
        ('Content-type', 'text/plain'),
        ('Set-Cookie', 'key=value; Path=/; HttpOnly; SameSite=strict'),
    ])
    return []


def app(
        environ: 'wsgiref.types.WSGIEnvironment',
        start_response: 'wsgiref.types.StartResponse',
) -> List[bytes]:
    print('Cookieの値', environ.get('HTTP_COOKIE'))

    for method, handler in [('GET', return_html), ('POST', give_cookie)]:
        if environ['REQUEST_METHOD'] == method:
            return handler(start_response)

    raise NotImplementedError()


if __name__ == '__main__':
    with wsgiref.simple_server.make_server('', 8080, app) as httpd:
        httpd.serve_forever()

起動すると8080番ポートで待ち受けます。リクエストを受けるとまずリクエストで送られてきたCookieを出力します。そしてそのリクエストがGETのときには10行目の関数が呼ばれJavaScriptを返し、POSTのときは24行目の関数が呼ばれクッキーをセットするヘッダーを返しています。GETのときに返されるJavaScriptは同サーバにPOSTリクエストを送信します。

ブラウザでブラウザでhttp://localhost:8080にアクセスすると次のような流れになります。

  1. ブラウザがGETリクエストを送信する。
  2. Cookieの値はNone
  3. JavaScriptを含んだHTMLが返される。
  4. JavaScriptがPostリクエストを送信する。
  5. Cookieの値はNone
  6. クッキーがセットされる

ここでブラウザに再読込させます。

  1. ブラウザがGETリクエストを送信する。
  2. Cookieの値 key=value
  3. JavaScriptを含んだHTMLが返される。
  4. JavaScriptがPostリクエストを送信する。
  5. Cookieの値 key=value
  6. クッキーがセットされる

HttpOnlyディレクティブ

クッキーにこれが指定されていると、JavaScriptがそのクッキーに一切触れられなくなります。そのためXSRFに対していくらか効果があります。有効にできることなら有効にしたいものです。今回私はこれを有効にしたいと思い、試してみたらできたという話です。