※下記記載内容は私が所属する企業でもLTした内容だが、自分の備忘録としてQiitaにも同じ内容を載せています
Chromeでは動くのに、Safariのローカル環境でだけログインできない… その原因と対策
現象:localhostにて「Safari」だけログインできない問題
-
使用技術
- フロントエンド:SolidJS、SolidStart
- バックエンド:Go
-
症状
- ChromeやFirefoxでは、ローカル環境(http://localhost )で問題なくログインできる
- Safariでだけ、ログインに成功した直後のAPI呼び出しで401 Unauthorizedエラーが発生し、未ログイン状態になってしまう
- バックエンドのログを見ると、ログイン処理自体は成功している
- なぜSafariだけ、セッションが維持できないのか?
-
原因調査
- バックエンドのログを詳しく見ると、ユーザー情報取得APIで「セッションキーが見つからない」というエラーが記録されていた
- これは、ブラウザがAPIリクエストにセッションCookieを添付していないことを意味する
対処方法:CookieのSecure属性が「false」であること
-
Cookieとは
- ブラウザに保存される小さなデータで、セッションIDはそのCookieに保存され、Webサイトがユーザーを識別するために使用されるものです。セッションIDは、サーバー側で管理されるユーザーの一連の行動(セッション)を特定するための情報
-
CookieのSecure属性とは?
- CookieにSecure属性を付けると、ブラウザに対して「このCookieは暗号化されたHTTPS接続でしか送信してはならない」と命令するセキュリティ機能
- これにより、中間者攻撃によるCookieの盗聴を防ぐことができる
-
問題だったコード(Go)
cookie := &http.Cookie{
Name: "session_key",
// ...
Secure: true, // HTTPSでのみCookieを送信
}
- ベストプラクティス:環境変数で切り替える
cookie := &http.Cookie{
Name: "session_key",
// ...
Secure: config.Get().Env != config.EnvLocal, // HTTPSでのみCookieを送信
}
- ローカル環境の現実
- http://localhost これは暗号化されていないHTTP接続
- Secure属性のルールと、実際の通信経路(HTTP)の間に矛盾が生じていた
なぜSafariだけ厳格なのか?
- Safariの挙動(仕様に忠実)
- Safariは、このルールを非常に厳格な解釈をする
- 「安全でないHTTP接続で、Secure属性付きのCookieを受け取った。これは矛盾であり危険だ」と判断し、安全のためにそのCookieを破棄する
- 結果:Cookieが保存されず、ログイン状態が維持できない
- Chrome / Firefoxの挙動(開発者に寛容)
- これらのブラウザは、開発者の利便性のためにlocalhostを特別扱いする
- localhostへの通信は外部ネットワークを経由しないため「安全なコンテキスト(Secure Context)」と見なし、HTTP接続であっても例外的にSecure属性付きCookieの保存を許可してくれる
まとめ
- Safariは、localhostでのCookieポリシーが他のブラウザより厳格
- http://localhost に対してSecure=trueのCookieを送ると、Safariはそれを無視する
- CookieのSecure属性は、環境変数を使ってローカル(false)と本番(true)で動的に切り替えるべき
参考資料