CookieのSameSite=Laxの動きを確かめるためにローカル開発環境をHTTPS化する

## 要約

[ローカル開発環境の https 化 | blog.jxck.io](https://blog.jxck.io/entries/2020-06-29/https-for-localhost.html) をやってみたら、思っていたより手軽だった話です。


## 現象

ある日、サイトAからサイトBに編集データを保存できなくなりました。
毎回サイトBでのログインが要求されます。一度ログインしても、サイドログインを求められます。
サイトBへの保存は、サイトAのJavaScriptでHTTPリクエストのPOSTメソッドを送っています。

```mermaid
sequenceDiagram
    participant User as ユーザー
    participant SiteA as サイトA (JavaScript)
    participant SiteB as サイトB

    User->>SiteA: サイトA を開く
    SiteA->>User: ページとJavaScriptを読みこむ
    User->>SiteA: 保存操作
    SiteA->>SiteB: HTTP Post request
    SiteB->>SiteA: HTTP Response
    SiteA->>User: 結果表示
```

ブラウザの開発ツールでみると、サイトBへのHTTPリクエストにCookieがついていません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15964/774c7bc4-f322-32d5-89f8-95b5e4326c5f.png)

このため、サイトBではユーザーのログイン状態を確認できずログインを要求します。

## 原因の候補

最近のブラウザやWebアプリケーションフレームワークは、CookieのSameSite属性のデフォルト値がLaxになっています[^1][^2][^3]。
これはサイトAのJavaScriptで作ったサイトB宛てのHTTPリクエストにサイトBのCookieをつけない制約です[^4][^5][^6]。
今回の事象にぴったりです。本現象の原因の候補です。
本当かどうか、実際に動かして確かめてみましょう。

[^1]: [Chrome 80が密かに呼び寄せる地獄 ～ SameSite属性のデフォルト変更を調べてみた #Chrome - Qiita](https://qiita.com/ahera/items/0c8276da6b0bed2b580c)
[^2]: [RailsでのCookieのSameSite, Secureの対応 #Ruby - Qiita](https://qiita.com/nobuo_hirai/items/b1b862fd1b06b927c87b)
[^3]: [SameSite=Lax is default value since Rails 6.1 · Issue #31 · pschinis/rails_same_site_cookie](https://github.com/pschinis/rails_same_site_cookie/issues/31)

[^4]: [SameSite Cookie の説明  |  web.dev](https://web.dev/articles/samesite-cookies-explained?hl=ja)
[^5]: [Cookie の性質を利用した攻撃と Same Site Cookie の効果 | blog.jxck.io](https://blog.jxck.io/entries/2018-10-26/same-site-cookie.html)
[^6]: [Google Developers Japan: 新しい Cookie 設定 SameSite=None; Secure の準備を始めましょう](https://developers-jp.googleblog.com/2019/11/cookie-samesitenone-secure.html)


## 命題

サイトBのレスポンスのSet-CookieヘッダーのSameSite属性をNoneに設定したとき、サイトAのJavaScriptで作ったHTTPリクエスト（特にPOSTメソッド）にサイトBのCookieをつけて送れるか確認します。

SameSite属性をNoneにするには、同時にSecure属性も設定する必要があります[^9]。
これは、HTTPSで接続したときのみCookieを送る設定です。
つまり、サイトBをHTTPSで動かす必要があります。

[^9]: [Set-Cookie - HTTP | MDN](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Set-Cookie)

## ローカル開発環境のHTTPS化

確認のためにHTTPS環境を用意したいです。
検証用にサーバーを用意するのは面倒です。
ローカル開発環境をHTTPS化します。

オレオレ証明書はつくりません。
ドメインを取得し、Let's Encryptから本物のSSL証明書を取得します。



### 環境

- WSL + Ubuntu
- Let's Encrypt
- Rails 7.0.8
- Puma



### ドメインの取得

さくらインターネットで取得しました。
過去に取得したことがあるので、さくらインターネットを選びました。
どこで取得しても良いと思います。

Let's Encryptの認証にDNSをつかうので、DNSが設定できると便利だと思います。

取得したドメインに次のレコードを追加します。

| 名前 | 値 |
|:-:|:-:|
| エントリ名  | localhost  |
| タイプ  | A  |
| データ  | 127.0.0.1  |

例えばドメインがledsun.jp の場合に、ブラウザで http://localhost.ledsun.jp を開いた時に http://127.0.0.1 が開くようにします。

つづいて localhost.ledsun.jp のSSL証明書をLet's Encryptから取得します。

### Certbotのインストール

Let's EncriptでSSL証明書するためにCertbotをインストールします[^7]。
Ubuntuではsnapdでインストール出来ます[^8]。

```sh
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
```

[^7]: [Let's EncryptのSSL証明書をDNS認証で発行してみた。（DNSはお名前.com） #googlecloud - Qiita](https://qiita.com/AkiQ/items/db4eb8c7106f109819f0)
[^8]: [Install Let's Encrypt SSL on Ubuntu with Certbot | InMotion Hosting](https://www.inmotionhosting.com/support/website/ssl/lets-encrypt-ssl-ubuntu-with-certbot/)

### SSL証明書の取得

次のコマンドを実行します。

```sh
sudo certbot certonly --manual -d localhost.ledsun.jp --preferred-challenges dns
```

次のようなメッセージが表示されます。

```sh
Requesting a certificate for localhost3.ledsun.jp

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please deploy a DNS TXT record under the name:

_acme-challenge.localhost.ledsun.jp.

with the following value:

Ik9IyRLRei27lWszeyYyWJmCVA13kO_7LGOucXcSuEc

Before continuing, verify the TXT record has been deployed. Depending on the DNS
provider, this may take some time, from a few seconds to multiple minutes. You can
check if it has finished deploying with aid of online tools, such as the Google
Admin Toolbox: https://toolbox.googleapps.com/apps/dig/#TXT/_acme-challenge.localhost3.ledsun.jp.
Look for one or more bolded line(s) below the line ';ANSWER'. It should show the
value(s) you've just added.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue
```

このメッセージから上手いこと情報を読み取って、DNSに次のレコードを追加します。

| 名前 | 値 |
|:-:|:-:|
| エントリ名  | _acme-challenge.localhost  |
| タイプ  | TXT  |
| データ  | Ik9IyRLRei27lWszeyYyWJmCVA13kO_7LGOucXcSuEc  |

この値はこの記事を書くために生成してるので、この値をそのまま使っても特に得することはありません。

レコードを追加してから30分くらいまってEnterキーを押します。
レコードを追加した直後はDNSに反映されないらしく失敗します。
どれくらいまで良いのかはわかりません。とりあえず30分待てば大丈夫なようです。

成功すると `/etc/letsencrypt/live/localhost.ledsun.jp` 配下にSSL証明書が作成されます。



### HTTPSでの起動

作成したSSL証明書をアプリケーションのディレクトリにコピーします。
root権限で作ったので、所有者を変更します。

```sh
sudo cp /etc/letsencrypt/live/localhost.ledsun.jp/privkey.pem .
sudo chown ledsun privkey.pem
sudo cp /etc/letsencrypt/live/localhost.ledsun.jp/fullchain.pem .
sudo chown ledsun fullchain.pem
```

`config/puma.rb`に証明書を設定します。

```rb:config/puma.rb
ssl_bind "0.0.0.0", "3001", {
  key: './privkey.pem',
  cert: './fullchain.pem'
}
```

`bin/rails s`でRailsアプリケーションを起動すれば、 https://localhost.ledsun.jp:3001/ で接続出来ます。




## 動作確認

Cookieの設定をSameSite=noneにして動作確認してみましょう。
`config/application.rb`に次の設定を追加します。

```rb:config/application.rb
config.action_dispatch.cookies_same_site_protection = :none
config.force_ssl = true
config.hosts << "localhost.ledsun.jp:3001"
```

`bin/rails s`でRailsアプリケーションを起動します。
このあとサイトAのアプリケーションからHTTPリクエストのPOSTメソッドを送ってCookieを送信できれば成功です。

1. ブラウザの開発コンソールログを開く
1. POSTメソッドを送る
1. ネットワークタブで、送信したPOSTメソッドを選択
1. Cookieタブを開く

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15964/d02678d0-0e6c-d263-9bd8-c5d2a394a021.png)

Cookieが送信されていることがわかります。

### SameSite=Laxのとき

比較用にSameSite=Laxのときの開発コンソールの表示を載せます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15964/774c7bc4-f322-32d5-89f8-95b5e4326c5f.png)

「フィルターで除外された要求 Cookie を表示する」をチェックすると送れなかったCookieを確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15964/1583c8b4-fd1b-7c9b-064d-551970cb6333.png)

SameSite属性がLaxであることがわかります。


## まとめ

ローカル開発環境をHTTP化することで、SameSite=noneの動作を確認できました。
オレオレ証明書ではないので、ブラウザでの警告等はでません。
ドメインの取得費用は掛かりますが、1つあれば色々なアプリケーションで活用できます。
今後のWeb技術はHTTPSを前提とした物が増えると思います。
個人で1つ開発用のドメインを持っておくのも良いのかも知れません。

## 参考

- [pschinis/rails_same_site_cookie: Manages the new SameSite=None behavior for Rails apps that use cookie-based authentication for cross-domain requests](https://github.com/pschinis/rails_same_site_cookie)
- [rails_same_site_cookie gemで、RailsアプリにChrome 80向けのSameSite属性を指定する #Ruby - Qiita](https://qiita.com/jnchito/items/1290a7b1dbf0ccf40c7b)
- [SameSite=None: Known Incompatible Clients](https://www.chromium.org/updates/same-site/incompatible-clients/)