CookieのSameSite属性とSecure属性について
初めまして。株式会社PRVENT開発部、バックエンドチームの横川です。
現在入社2年目で、普段はGoやRubyを書いて仕事してます。
今回はCookieのSameSite属性とSecure属性について紹介したいと思います。
なぜ書こうと思ったか
現在、『Mymonitor』という弊社のスタッフと取引先が利用するプロダクトのリプレースを進めており、Cookie初心者の自分がCookieを使ったログイン管理を実装しました。
そこで直面したエラーのおかげでCookieについて調べ上げCookieを完全に理解したので、今回直面したエラーの原因でもあるSameSite属性とSecure属性の挙動について記事にすることにしました。
そもそも Cookieとは
ブラウザで何かのサイトを閲覧した際にそのサイトのサーバーが発行してブラウザへ送信, 保存するテキスト情報です。
そのテキスト情報は、次またそのサイトへアクセスした際に毎回自動でブラウザからサーバーへ送信されるようになっていて使い方は様々です。
具体的にはサーバー側からのレスポンスにSet-Cookieヘッダを付与することでブラウザにセットされます。
ログアウトできない...
MymonitorはフロントエンドにNext.js, バックエンドにGoを採用しており分業体制で開発を進めています。
ある程度機能を作ったところで一旦AWSに載せてみることになったのですが、ローカル環境では問題なく動作していたログアウトがAWS環境だと動作しませんでした...
ただこのエラーを機に、Cookieについて調べ上げることとなります。
原因はSamesite属性とSecure属性
確認したところ、サーバー側で発行するログアウト時のCookieにSameSite属性とSecure属性を設定していなかったのが原因でした。
ちなみにログイン、ログアウト時の意図した挙動は以下です。
機能 | 詳細 |
---|---|
ログイン | 有効期限が3時間のCookieをサーバー側で発行しブラウザに保存する |
ログアウト | 有効期限が切れたCookieをサーバー側で発行しブラウザに保存する |
認証 | リクエストの度にサーバー側でCookieを受け取りその値でアクセスコントロール |
有効期限が切れたCookieは、ブラウザに保存すると同時に消えることでログアウトを実現できます。
今回は消えるはずのCookieがログアウト後も残ってしまっていました。
その時のソースコード
// ログイン時のcookie cookie := http.Cookie{Name: name, Value: token, Expires: time.Now().Add(3 * time.Hour), HttpOnly: true, Secure: true, SameSite: http.SameSiteNoneMode} // ログアウト時のcookie cookie := http.Cookie{Name: name, Value: "", HttpOnly: true, MaxAge: -1}
すっぽり抜けてます。
ただ、この時Cookie初心者だった自分は SameSite属性 == クロスオリジンでCookieやりとりするときはNoneにしないといけない
と誤った理解をしてしまっていました。
なのでローカルもAWS環境もクロスオリジンだったのに、なぜAWS環境だけログアウトできないのかこの時点ではまだわかっていません 笑
SameSite属性とSecure属性ってなんぞや
Cookieには発行する際にいくつか属性を付与できます。(以下一部抜粋
名前 | 解説 |
---|---|
Name | Cookieの名前 |
Value | Cookieの値 |
Expires | 有効期限(日) |
MaxAge | 期限までの秒数 |
HttpOnly | 付与するとJavaScriptからアクセスできなくなる |
Secure | 付与すると httpsの通信でのみ送信する |
SameSite | cross-site(クロスサイト)の通信でもCookie送信します?の 設定 |
解説にも書きましたが、今回取り上げるSecure属性は付与することによりそのCookieはhttps通信でなければ、ブラウザ, サーバ間で送信されなくなります。
SameSite属性は、このあと書きますが設定によってブラウザ, サーバー間のCookie送信をクロスサイトでも行うかの設定ができます。
SameSite属性に設定できる値
名前 | 効果 | 詳細 |
---|---|---|
Strict | 同一サイトでのみCookie送信 | セキュリティ強 |
Lax | クロスサイトの場合HTTPメソッドがGETなど特定の条件下でのみCookie送信 | Chromeでは未指定だとこれになる。 セキュリティ中 |
None | クロスサイト、同一サイト関係なく送信する | Secure属性が必須。セキュリティ弱 |
今回だと、フロント側のURLとサーバー側のURLの関係がクロスサイトかどうかが重要で自分はここを理解してませんでした。そして当初の理解のSameSite属性 == クロスオリジンでCookieやりとりするときはNoneにしないといけない
ではなく SameSite属性 == クロスサイトでCookieやりとりするときはNoneにしないといけない
と知りました。(そもそも属性の名前が SameSite(同一サイト)になってるやろ!って話ですね)
ちなみにcross-site(クロスサイト)かsame-site(同一サイト)かの定義は以下の通りです。
引用:https://zenn.dev/agektmr/articles/f8dcd345a88c97
トップレベルドメイン+セカンドレベルドメインの組み合わせが違うかどうかのようです。
今回の状況
環境 | フロント | バック | 詳細 |
---|---|---|---|
ローカル | http://localhost:3000 | http://localhost:8000 | same-site なのでCookie送信できる |
AWS | https://hogehoge.amplifyapp.com/ | https://fugafuga.com/ | cross-site & SameSite属性が 未指定(Lax) & リクエストメソッドがPOST なのでCookie送信できない |
ローカル環境ではポート番号が違うだけで同一サイトになるため、SameSite属性が未指定(Lax)でも問題なくCookieを送信できる。
AWS環境では、まだ載せたばかりだったこともありドメインも違うのでクロスサイトになり、SameSite属性が 未指定(Lax)のログアウト処理だけうまく動かなかったわけです。
http通信のローカル環境でなぜログインできてたのか
SameSite属性は完全に理解したところですが、もう一つ疑問が。
そう。Secure属性です。Secure属性は付与すると、httpsでしかCookie送信しないはず...
↓もう一度その時のソースコード
// ログイン時のcookie cookie := http.Cookie{Name: name, Value: token, Expires: time.Now().Add(3 * time.Hour), HttpOnly: true, Secure: true, SameSite: http.SameSiteNoneMode} // ローカル環境のhttp通信では送信しないはず... // ログアウト時のcookie cookie := http.Cookie{Name: name, Value: "", HttpOnly: true, MaxAge: -1}
localhost は例外
ブラウザによっては localhost だと httpsの要件が無視されるとのことでした。
※自分はChromeを使ってます
引用:https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Set-Cookie#sect4
開発者のこと考えてってことだよね。ありがてぇ... 笑
まとめ
- ローカル環境では同じlocalhostでsame-siteのため SameSite属性が未指定(Lax)でもCookie送信できた
- cross-siteでCookie 送信する場合は、SameSite属性をNone、Secure属性をtrueにするべし
- Secure属性は localhost だと 無視される(ブラウザによる)
最後に
なんだかんだで今では、AWS環境でフロント側とサーバー側で同じドメインを使い、SameSite属性をStrictにして動かしています。
有名なサーバー攻撃手法のXSSやCSRFの対策として、SameSite属性やHttpOnly属性の設定は必須です!!正しい知識で安全にCookie使っていきましょー!!