はじめに
ログイン機能やカートなど「セッション」を持つサイトでは、Cookieの守りが甘いとセッション乗っ取り(セッションハイジャック)やCSRFの被害に直結します。逆に言えば、Secure / HttpOnly / SameSite の3つの属性を正しく付けるだけで、これらの典型的なリスクを大きく下げられます。
この記事では、3属性それぞれの役割と間違えやすいポイント、PHP / nginx / Apache / WordPress での設定方法、そして設定後に「本当に付いているか」を確認する方法までをまとめます。対象は、自社サイトやアプリを運用・制作する初〜中級エンジニア/Web担当者です。
値はサイトの認証フロー(SSO・外部リダイレクト・サブドメイン構成など)によって最適解が変わります。本番反映前に必ず検証環境で動作確認してください。
まず全体像:3つの属性の役割
| 属性 | 役割(何を防ぐか) | 付けないと起きること |
|---|---|---|
Secure |
HTTPS接続のときだけCookieを送信する | HTTP通信で平文のまま盗聴され、Cookieを盗まれる |
HttpOnly |
JavaScriptからCookieを読めなくする | XSSが起きた際にdocument.cookieでセッションを抜かれる |
SameSite |
別サイト起点のリクエストでの送信を制御する | クロスサイトでCookieが送られCSRFの起点になる |
3つは役割が重なりません。3つセットで初めて「盗聴・XSS経由の窃取・クロスサイト送信」をそれぞれ塞げると考えてください。
各属性の詳細と推奨
Secure
CookieをHTTPS接続のときだけブラウザが送信するようにします。常時HTTPSが前提の今、セッション系Cookieには必須です。
-
注意: HTTP(平文)では送られません。ローカル開発を
http://localhostで行っていると、Secure付きCookieが送られずログインできない、という形でハマりがちです(開発環境だけ条件分岐する等の対応が必要)。
HttpOnly
JavaScriptの document.cookie からそのCookieを読めなくします。XSSが混入しても、セッションCookieの値を盗み出す経路を一つ塞げます。セッション・認証系Cookieには付けるのが基本です。
- 注意: HttpOnlyはCSRF対策ではありません(ブラウザはCookieを自動送信するため)。CSRF対策は後述のSameSiteやCSRFトークンで別途行います。
- JavaScriptから読む必要があるCookie(例: 古い設計でCSRFトークンをJSから参照している等)はHttpOnlyにできません。その場合は「セッション本体は別Cookieにして、それだけHttpOnly」など設計を分けるのが安全です。
SameSite
別サイトを起点とするリクエストにCookieを付けるかどうかを制御します。値は3種類です。
| 値 | 挙動 | 主な用途 |
|---|---|---|
Strict |
別サイトからのリクエストでは一切送らない | 最も堅い。ただし副作用に注意(下記) |
Lax |
別サイトからの**トップレベル遷移(GET)**でのみ送る | 多くのサイトの実用的な既定 |
None |
クロスサイトでも常に送る。Secure必須
|
埋め込みや別ドメインAPIなどクロスサイト前提の用途 |
- 主要ブラウザは、SameSiteを指定しない場合に
Lax相当として扱います(Chromeはバージョン80以降この挙動)。とはいえ挙動を明示するため、意図する値を必ず付けるのが推奨です。 -
SameSite=NoneはSecureがないとブラウザに拒否されます。 Noneを使うならHTTPS+Secureがセットです。 -
Strictの落とし穴: 外部サイトのリンクから自サイトに遷移した「直後」はCookieが送られないため、ログイン状態に見えない、という挙動になります。SSO・OAuth・決済リダイレクトのように外部から戻ってくるフローがあるサイトでは、Laxのほうが無難です。
補足:Cookieプレフィックス __Host- / __Secure-
Cookie名の先頭に特定の接頭辞を付けると、ブラウザ側が属性の要件を強制してくれます。
-
__Secure-…Secure必須。 -
__Host-…Secure+Path=/+Domain属性なし(=発行元ホストに限定)。最も堅い。
要件を満たさないとブラウザがそのCookieを無視するため、誤設定の検出にも役立ちます。対応ブラウザは広いですが、既存Cookie名の変更はアプリ改修を伴うため計画的に。
環境別 設定方法(コピペ用)
Cookieは基本的にアプリケーションが発行するものなので、まずはアプリ側で付けるのが最も確実です。サーバ側(nginx/Apache)での一括付与は、アプリを直せない場合の補完と考えてください。
アプリ側:PHP(最も確実)
任意のCookieを発行する場合(PHP 7.3以降のオプション配列):
setcookie('token', $value, [
'expires' => time() + 3600,
'path' => '/',
'secure' => true, // HTTPSのみ
'httponly' => true, // JSから不可視
'samesite' => 'Lax', // 'Strict' / 'Lax' / 'None'(NoneはSecure必須)
]);
PHPのセッションCookieは、設定で一括指定できます(php.ini):
session.cookie_secure = 1
session.cookie_httponly = 1
session.cookie_samesite = "Lax"
実行時に指定する場合(session_start() の前に):
session_set_cookie_params([
'secure' => true,
'httponly' => true,
'samesite' => 'Lax',
]);
session_start();
samesiteオプションはPHP 7.3以降で利用可能です。それ以前のバージョンでは別途ヘッダ操作が必要になります。
nginx(リバースプロキシ構成)
バックエンドが返すCookieに属性を付与するには proxy_cookie_flags(nginx 1.19.3以降)が使えます。
location / {
proxy_pass http://backend;
proxy_cookie_flags ~ secure httponly samesite=lax;
}
-
重要な制約:
proxy_cookie_flagsはproxy_passで受けた応答に対する機能です。PHP-FPMをfastcgi_passで動かす一般的なWordPress構成には適用されません(fastcgi向けの同等ディレクティブが無い)。その場合はアプリ側(PHP)で設定するのが確実です。
Apache(.htaccess / mod_headers)
mod_headers の edit で、レスポンスの Set-Cookie に属性を追記できます(Apache 2.4.11以降)。
<IfModule mod_headers.c>
Header always edit Set-Cookie ^(.*)$ "$1; Secure; HttpOnly; SameSite=Lax"
</IfModule>
-
注意1: この書き方はすべての
Set-Cookieに無条件で追記します。既にSameSite等が付いているCookieには属性が二重になり得ます。対象を絞るなら、特定Cookie名にマッチする正規表現にしてください。 - 注意2: HTTPでアクセスするとSecure付きCookieはブラウザに無視されます。開発環境などHTTPで触る場面でログインできなくなることがあります。
- 事前に
a2enmod headers && systemctl restart apache2(Debian/Ubuntu系の例)。.htaccessで効かせるにはAllowOverrideの許可が必要です。
WordPress
- WordPressの認証Cookieは既定で
HttpOnlyが付いています。 -
Secureにするには、常時HTTPS化したうえでwp-config.phpに次を追加します。
define('FORCE_SSL_ADMIN', true);
-
SameSiteはWordPressコアに「これ一つで指定できる定数」が用意されていません。PHPのsession.cookie_samesiteはPHPセッションには効きますが、WP独自の認証Cookieには別途の対応が要ります。手早く全Cookieに付けたい場合は、上のApacheHeader editでまとめて付与する方法が現実的です(副作用に注意)。 - いずれの方法でも、後述の方法で実際に付与されているかを必ず確認してください。キャッシュやプラグインの挙動で意図通りにならないことがあります。
設定時の注意点・副作用(重要)
-
SameSite=Strictは外部サイトからのリンク遷移直後にログイン状態が引き継がれない。SSO・OAuth・決済リダイレクトを使うサイトはLaxを基本に。 -
SameSite=NoneはSecure必須。HTTPS前提でしか使えない。 -
SecureはHTTPでは送信されない。ローカル開発(http)で詰まりやすい典型ポイント。 -
HttpOnlyはCSRF対策ではない。CSRFはSameSiteやCSRFトークンで別途対策する。 -
Domain属性を広く付け過ぎると意図しないサブドメインへCookieが送られる。限定したいなら__Host-プレフィックスが有効。 - ブラウザではクロスサイト(サードパーティ)Cookieの制限が段階的に進んでいる。クロスサイト前提の実装は将来の挙動変更の影響を受ける可能性があるため、依存度は環境により見直しが必要。
設定後の確認方法
まずは curl で生の Set-Cookie を確認するのが確実です。
curl -sI https://example.com/login | grep -i set-cookie
ブラウザのDevToolsでも確認できます。
-
DevTools → Application(またはStorage)→ Cookies:各Cookieの
Secure/HttpOnly/SameSite列を一覧で確認。 - DevTools → Issues/コンソール:SameSite未指定などの警告が出ることがある。
外形チェックには無料ツールも便利です。評価軸が異なるので、複数併用すると把握しやすくなります。
- Mozilla Observatory:ヘッダに加えてCookie属性も採点し、改善提案を出してくれる。
- OWASP ZAP:ローカルで動かせるOSSスキャナ。Cookie属性の欠落を受動的に検出できる。
- サイトドック(sitedock.jp):URLを入れると登録不要で、Cookie属性やヘッダ・SPF/DMARCなどをパッシブに健診してスコア化する無料サービス。設定後の抜け漏れを第三者視点でまとめて確認したいときの手段の一つとして。
ツールごとに判定基準が微妙に違うため、「あるツールで満点でない=即危険」とは限りません。指摘内容を自サイトの認証フローに照らして取捨選択するのが現実的です。
まとめ
-
Secure(盗聴)・HttpOnly(XSS経由の窃取)・SameSite(クロスサイト送信)は役割が別。3つセットで考える。 - セッション・認証系Cookieは原則
Secure; HttpOnly; SameSite=Laxから。 -
SameSite=Strictは外部リダイレクト系フローで副作用が出やすい。SSO/決済があるならLax。 -
SameSite=NoneはSecure必須(なければブラウザが拒否)。 - Cookieはアプリ側で付けるのが最も確実。nginx
proxy_cookie_flagsはproxy構成限定、ApacheHeader editは全Cookieに無条件追記する点に注意。 - WordPressの認証Cookieは
HttpOnly既定。SecureはFORCE_SSL_ADMIN、SameSiteはサーバ/PHP側の補完が必要。 - 反映後は
curl -IとDevTools(Application > Cookies)、Mozilla Observatory / OWASP ZAP などで実際に付いているかを必ず確認する。
想定タグ: Security Cookie SameSite PHP WordPress
想定読了時間: 約7〜9分
難易度: 初〜中級
関連記事
- Webサイトに最低限入れるべきHTTPセキュリティヘッダ7種と設定例 — Cookieと合わせて整えたいレスポンスヘッダ
- WordPressセキュリティ 最低限やることチェックリスト10
- 兵庫県の企業サイト307件を外形調査したら…(Cookie Secure欠落43.8%)