SameSiteは、Cookieを同一サイト内の通信だけに送るのか、別サイトからのリクエストにも送るのかを制御する属性です。ログインセッションでCookieを使う場合、SameSite、Secure、HttpOnly、さらにフロント側のcredentials設定やCORS設定が噛み合っていないと、ログインしたのにCookieが送られず、状態が維持されない原因になります。SameSiteはCookieの送信範囲を決める設定であり、ログイン不具合を切り分けるうえで重要な基本知識です。

1. SameSite Cookieとは何か
1-1. Cookieを送る範囲を制御する属性
SameSiteは、Cookieをどの種類のリクエストで送るかを制御する属性です。特に、同じサイト内の通信だけに送るのか、別サイトから来た通信にも送るのかを決める役割があります。
ログイン機能では、サーバーがセッションIDをCookieへ入れてブラウザへ返し、その後のリクエストでそのCookieが送られることで「この人はログイン済み」と判断します。ところが、Cookieはどんな通信でも自動で送られると危険なので、ブラウザはSameSite属性を見て送信範囲を制御します。つまり、SameSiteはログイン状態そのものを作るものではなく、作られたCookieをどこまで送るかを調整する仕組みです。
初心者のうちは「Cookieを保存しているのにログインが続かない」という場面で詰まりやすいですが、その原因のひとつがSameSiteです。ログイン後にCookie自体は発行されていても、次の通信で送られなければ、サーバーからは未ログインに見えます。
1-2. 同一サイトとクロスサイトの違い
SameSiteを理解するには、同一サイトとクロスサイトの違いを押さえることが大切です。ここが曖昧だと、設定を見ても何が起きているか分かりにくくなります。
同一サイトとは、ざっくり言えば同じサイトの文脈で行われる通信です。一方、クロスサイトは、別サイトから来た遷移やリクエストです。ここで重要なのは、単に「別ドメインかどうか」だけではなく、ブラウザがサイトのまとまりとしてどう見ているかです。開発中はフロントとAPIを別の場所で動かすことが多いため、自分では近い構成に見えても、ブラウザからはクロスサイト扱いになることがあります。
たとえば、フロントが https://app.example.com、APIが https://api.example.com のような構成や、ローカルでポート違いの構成にしていると、SameSiteやCookie送信の挙動で混乱しやすくなります。Cookieが送られないときは、「同じサービスだから大丈夫」と思い込まず、ブラウザがどう判定しているかを見る必要があります。
1-3. ログイン状態が維持されない問題との関係
ログイン状態が維持されない原因のひとつとして、Cookieは保存されたが、その後の通信で送られていないというケースがあります。SameSiteはこの「送られるかどうか」に直接関係します。
たとえば、ログインAPIのレスポンスでセッションCookieがセットされても、その後のAPI通信がクロスサイト扱いで、SameSite設定が合っていなければCookieが送られません。サーバー側はCookieを受け取れないため、毎回「ログインしていない」と判断します。つまり、ログイン処理そのものが成功していても、セッション維持が失敗することがあります。
この問題は、画面上では「ログインした直後にまたログイン画面へ戻る」「ページ更新したら未ログインになる」と見えることが多いです。Cookieを保存できたかだけでなく、その後のリクエストで送られているかまで確認することが重要です。
2. SameSiteの3つの値
2-1. SameSite=Lax:基本になりやすい設定
SameSite=Laxは、比較的安全性と使いやすさのバランスが取りやすい設定です。そのため、最初の基本設定として使われることが多いです。
Laxでは、同一サイト内の通常の通信ではCookieが送られます。一方で、クロスサイトの一部の通信では制限されます。完全に閉じるわけではありませんが、何でも送るわけでもないため、ログインセッションCookieの基本設定として採用されやすいです。つまり、「必要以上に広げず、一般的なWeb利用で困りにくい」位置づけです。
ただし、フロントとAPIが分かれた構成や、クロスサイト前提の認証フローではLaxでは足りないことがあります。ローカルでは動くのに本番では構成が変わって急に維持できなくなる場合は、この前提を疑う価値があります。
2-2. SameSite=Strict:より厳しく制限する設定
SameSite=Strictは、より厳しくCookie送信を制限したいときの設定です。安全側に寄りやすい反面、ログイン維持の使い勝手に影響しやすいです。
Strictでは、クロスサイト由来の文脈でCookieを送らない方向へ強く寄せます。そのため、外部サイトからの遷移や特定の埋め込み・連携で、期待通りにログイン状態が引き継がれないことがあります。セキュリティ上は有効な場面がありますが、すべてのログインCookieへ機械的に付ければよい、というものではありません。
たとえば、外部リンク経由でサービスへ戻ってきたときの挙動や、認証連携を含む構成ではStrictが厳しすぎることがあります。Strongな設定に見えても、ユーザー体験が崩れるなら全体として良い設計とは言えません。厳しさと使いやすさの両方を見る必要があります。
2-3. SameSite=None:クロスサイトで送るための設定
SameSite=Noneは、クロスサイトの通信でもCookieを送る必要があるときに使う設定です。フロントとAPIが別サイト扱いになる構成では重要になることがあります。
たとえば、別オリジンのフロントエンドからAPIへログイン状態付きでアクセスしたい場合、Cookieをクロスサイトで送る必要があります。このときSameSite=Noneを使います。ただし、Noneは自由に使えるわけではなく、Secure属性もセットで必要です。つまり、Noneは「広く送れる設定」ですが、そのぶん条件付きです。
Set-Cookie: sessionId=abc123; Path=/; SameSite=None; Secure; HttpOnly
この例では、クロスサイト送信を許可しつつ、HTTPS通信とJavaScriptからの読み取り制限も付けています。注意点は、SameSite=None だけ付けて安心しないことです。Secureがないとブラウザに拒否されるため、Noneを使うなら必ずセットで考える必要があります。
3. Secure / HttpOnlyとの違い
3-1. SecureはHTTPS通信でのみCookieを送る属性
Secureは、HTTPS通信のときだけCookieを送るようにする属性です。SameSiteとは役割が違い、「どのサイトから来たか」ではなく「安全な通信路かどうか」を見ています。
もしSecureがなければ、HTTPのような暗号化されていない通信でもCookieが送られる可能性があります。すると、通信経路上で情報が見られるリスクが高まります。そのため、特にログインセッションのような重要なCookieには、HTTPS前提のSecureを付けるのが基本です。
SameSite=Noneを使う場合はSecureが必須なので、この2つはよく一緒に出てきます。ただし意味は別です。Secureは通信経路の安全性、SameSiteはCookie送信の範囲を担当している、と分けて理解すると混乱しにくくなります。
3-2. HttpOnlyはJavaScriptからCookieを読ませない属性
HttpOnlyは、ブラウザのJavaScriptからCookieを読み取れないようにする属性です。これもSameSiteやSecureとは別の役割です。
もしHttpOnlyがないと、JavaScriptから document.cookie でCookieへアクセスできる場合があります。すると、XSSのような問題が起きたときに、セッションCookieを盗まれるリスクが高まります。HttpOnlyを付けることで、少なくともJavaScript経由で直接読み取ることは防ぎやすくなります。
ここで大事なのは、HttpOnlyは「送るかどうか」を決める属性ではないことです。Cookie送信の範囲を制御するSameSiteとは役割が違います。SecureとHttpOnlyを同じ意味として覚えてしまうと、トラブル時に切り分けが難しくなります。
3-3. SameSite、Secure、HttpOnlyを組み合わせて考える
Cookie設計では、SameSite、Secure、HttpOnlyを別々ではなく組み合わせで考えることが大切です。どれか1つだけ見ても、ログイン状態の問題は解決しないことが多いです。
SameSiteは送信範囲、SecureはHTTPS限定、HttpOnlyはJavaScriptからの読み取り制限です。それぞれ守る対象が違うため、ログインセッションでは複数を一緒に使うことが多いです。つまり、「Cookieが送られない」「Cookieが見えてはいけない」「安全な通信だけにしたい」という別の要求を組み合わせて満たします。
Set-Cookie: sessionId=abc123; Path=/; SameSite=Lax; Secure; HttpOnly
このように組み合わせることで、通常のログインCookieとしてかなり基本的な形になります。注意点は、属性をテンプレートのように丸ごとコピーして終わりにしないことです。クロスサイト通信が必要なのか、ローカル開発ではどうするのかまで考えないと、本番や別環境で思わぬ不具合が出やすくなります。
4. Cookieが送られない典型パターン
4-1. SameSite=NoneなのにSecureがない
Cookieが送られない典型例として、SameSite=Noneを付けたのにSecureを付けていないケースがあります。これはかなりよくある設定ミスです。
ブラウザは、SameSite=NoneのCookieについて、Secureも一緒に付いていないと受け入れない方向で動きます。つまり、サーバー側では「クロスサイトで送るつもり」で返していても、ブラウザ側では保存や送信の対象にならないことがあります。その結果、ログイン直後はうまくいったように見えても、次の通信でCookieが存在しないように見えることがあります。
Set-Cookie: sessionId=abc123; Path=/; SameSite=None
この例は不十分な設定です。注意点は、SameSite=Noneにした時点でSecureも必要になることです。ローカルHTTP環境ではこの条件が満たしにくいため、「開発中だけNoneが動かない」という混乱も起きやすいです。
4-2. fetchやaxiosでcredentials設定が不足している
Cookieが送られない原因はCookie属性だけではなく、フロント側のリクエスト設定不足でも起きます。特にfetchやaxiosでcredentials指定を忘れると、Cookie付き通信は成立しません。
ブラウザは、別オリジンへのリクエストでCookieを自動送信しない設定になっていることがあります。そのため、フロント側で「認証情報を含めて送る」指定が必要です。Cookie自体が正しく発行されていても、この設定が足りないと実際のAPI呼び出しで送られません。
fetch('https://api.example.com/profile', {
method: 'GET',
credentials: 'include'
});
この例では、Cookieなどの認証情報を含めて送る指定をしています。注意点は、サーバー側のCookie設定だけ見て満足しないことです。Cookieが送られないときは、フロントのHTTPクライアント設定も同時に確認する必要があります。
4-3. CORS側のcredentials許可が不足している
Cookieありのクロスサイト通信では、CORS設定側でもcredentials許可が必要です。SameSiteやフロント設定が正しくても、ここが欠けるとブラウザはレスポンスを扱えません。
サーバー側では Access-Control-Allow-Credentials: true を返し、さらに許可するOriginも明示する必要があります。ここで Access-Control-Allow-Origin: * のような設定では、credentialsあり通信と噛み合いません。つまり、Cookie、fetch設定、CORSヘッダーの3つがセットで成立する必要があります。
Access-Control-Allow-Origin: https://frontend.example.com
Access-Control-Allow-Credentials: true
この例では、特定のフロントからのCookieあり通信を許可しています。注意点は、Cookieが送られない原因をすべてSameSiteのせいにしないことです。ブラウザでは、CORSのcredentials不足でも「ログインが維持されない」ように見えるため、切り分けが重要です。
5. ローカル開発と本番環境の落とし穴
5-1. localhostとHTTPS環境の違い
ローカル開発と本番環境では、HTTPかHTTPSかの違いがCookie挙動へ大きく影響します。ここが「ローカルでは動くのに本番で動かない」またはその逆を生みやすいポイントです。
本番ではHTTPS前提でSecure付きCookieが正しく送られる一方、ローカルではHTTPで動かしていることが多いです。そのため、本番向けにSecure必須の設計へしたらローカルでは送られない、逆にローカル向けの緩い設定のままだと本番で不十分、というズレが起きます。SameSite=NoneとSecure必須の組み合わせも、この差を分かりにくくします。
開発時は「本番と何が違うか」を具体的に見ることが大切です。特にHTTPS、ドメイン、CORS、Cookie属性はセットで変わりやすいので、単に「環境差がある」ではなく、どの条件が違うかを確認する必要があります。
5-2. ドメイン、サブドメイン、ポート違いで起きる混乱
Cookieまわりでは、ドメイン、サブドメイン、ポート違いが混乱の原因になりやすいです。見た目は近い構成でも、ブラウザの扱いはかなり変わります。
たとえば、app.example.com と api.example.com、あるいは localhost:3000 と localhost:8080 のような構成では、開発者が「同じサービス」と思っていても、ブラウザは別の条件で判定します。その結果、Cookieがセットされる範囲や送信条件が想像とズレることがあります。特にCORSとSameSiteが絡むと、どちらの問題なのか見分けにくくなります。
ログインが維持されないときは、URL全体をなんとなく見るのではなく、ドメイン、サブドメイン、ポート、HTTPSかどうかを分解して確認すると原因に近づきやすいです。ローカルでは1台のPC上で動いていても、ブラウザから見た条件は別物です。
5-3. DevToolsでCookie送信を確認する方法
Cookieまわりの不具合を調べるときは、ブラウザのDevToolsで実際にCookieが保存されているか、送られているかを見るのが基本です。感覚で追うとかなり迷いやすい領域です。
Applicationタブでは保存されているCookieの属性を確認できます。Networkタブでは、リクエストにCookieヘッダーが付いているか、レスポンスでSet-Cookieが返っているかを見られます。つまり、「Cookieは発行されたのか」「保存されたのか」「次回送られたのか」を順番に確認できます。
初心者のうちはコンソールエラーだけ見て終わりがちですが、Cookie問題では保存と送信の両方を見ることが重要です。特にログインAPIのレスポンスと、その次のプロフィール取得APIのリクエストをセットで見ると、どの段階で止まっているかを切り分けやすくなります。
6. まとめ
6-1. SameSite Cookieの使い分け
SameSite Cookieを理解するときは、Lax、Strict、Noneを安全性と使い方で使い分けることが大切です。どれか1つが常に正解というわけではありません。
Laxは一般的なログインセッションでバランスが取りやすく、Strictはより厳しく制限したい場面向きです。Noneはクロスサイト通信でCookieを送りたいときに必要ですが、Secure必須で、フロントやCORSの設定も合わせる必要があります。つまり、SameSiteは単独で決めるのではなく、通信構成と一緒に考えるべき属性です。
また、SecureはHTTPS限定、HttpOnlyはJavaScriptから読ませない属性なので、SameSiteとは役割が違います。ログインCookie設計では、この3つを混同せずに組み合わせることが実務では重要です。
6-2. Cookieが送られないときの確認チェックリスト
最後に、Cookieが送られないときに最低限確認したいポイントをまとめます。すべてをSameSiteの問題と決めつけず、順番に切り分けることが大切です。
- Set-CookieでSameSiteの値は適切か
- SameSite=NoneならSecureが付いているか
- Secure付きCookieをHTTP環境で使おうとしていないか
- HttpOnlyとSameSiteの役割を混同していないか
- fetchやaxiosでcredentials設定があるか
- CORSでAccess-Control-Allow-Credentialsが返っているか
- 許可Originが
*になっていないか - DevToolsでCookieの保存と送信を両方確認したか
ログイン状態が維持されない問題は、Cookieの保存、送信、CORS、HTTPS条件が少しずつ噛み合っていないことで起きます。SameSiteをきっかけに、Cookie全体の役割を整理して理解すると、実務での詰まり方がかなり減ります。
7. 参考リンク
- MDN: Set-Cookie
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie - MDN: SameSite cookies
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite - MDN: Secure cookie configuration
https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/Cookies - MDN: Cross-Origin Resource Sharing (CORS)
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS