はじめに
Webアプリ開発でJWTのリフレッシュトークンをCookieで管理する際に、
res.cookie の設定で以下の2点にハマったので備忘録としてまとめます。
-
sameSiteの意味 - localhostでの
secureの挙動
結論
res.cookie("refreshToken", refreshToken, {
httpOnly: true,
secure: true, // ← ローカルでも送られる
sameSite: "None", // ← クロスサイトリクエストに必須
path: "/",
maxAge: 7 * 24 * 60 * 60 * 1000, // 7日
});
ポイント
-
secureはローカル開発環境でもtrueでOK(HTTPSでなくても送られる) -
sameSiteをNoneにしないとクロスサイトリクエストでCookieが送信されない
前提
- フロントエンドとバックエンドを別ドメインで運用
- JWTのアクセストークンは
localStorage管理 - JWTのリフレッシュトークンはCookie管理
問題1: secure: true はローカルでも送られるの?
最初は以下のように secure: true にしていました。
res.cookie("refreshToken", refreshToken, {
httpOnly: true,
secure: true,
});
しかし、
「ローカルではsecure: trueだとCookieが送られないのでは?」
と疑問に思っていました。
実際に試した結果・・・
-
localhost (
http://localhost:3000など) でもCookieは送られました 🎉 - ローカルホストをHTTPS相当として扱う仕様になっているそうです.
参考:
【Rails5】CookieにSecure属性を付与してもhttp通信(http://localhost:3000)でCookieが送信される
つまり、本番と開発ともに常に secure: true で統一してOKです。
問題2: 開発時はCookieが送られるのに,本番環境では送られない.
最初は以下のように sameSite="strict" と設定していました
res.cookie("refreshToken", refreshToken, {
httpOnly: true,
secure: true,
sameSite: "strict",
path: "/",
maxAge: 7 * 24 * 60 * 60 * 1000,
});
この状態でデプロイすると、開発環境ではcookieが送信されるのに本番環境ではcookieが送信されないという問題にあたりました.
結論としては,sameSiteによるクロスサイトリクエスト管理が適切ではありませんでした.
開発時は
- フロント:
localhost:5173 - バック:
localhost:3000
本番環境は
- フロント:
https://○○-frontend.vercel.app - バック:
https://○○-api.vercel.app
でした.
そのため,開発環境ではドメインがlocalhostと一緒なのでsameSite: "strcit"にしても動作しましたが,開発環境では別ドメインになるので,クッキーが送られないということでした.
フロントエンドとバックエンドが別ドメインの場合は,必ず sameSite: "None" を設定する必要があります。
res.cookie("refreshToken", refreshToken, {
httpOnly: true,
secure: true,
sameSite: "None",
});
SameSite のオプションまとめ
| 値 | 説明 |
|---|---|
Strict |
同一ドメインのみCookie送信。完全クロスサイト拒否。 |
Lax |
クロスサイトのGETはOK、POSTなどは拒否。 |
None |
完全クロスサイト許可。Secureが必須。 |
最終形
最終的に以下の設定で解決しました。
res.cookie("refreshToken", refreshToken, {
httpOnly: true, // JSからアクセス不可
secure: true, // HTTPSのみ(localhostは例外)
sameSite: "None", // クロスサイトリクエスト許可
path: "/", // Cookie有効範囲
maxAge: 7 * 24 * 60 * 60 * 1000, // 7日
});
まとめ
-
secure: trueは localhostでも送られる(最近のブラウザ仕様) - 別ドメインでCookieを送るには
sameSite: "None"が必須
res.cookie("refreshToken", refreshToken, {
httpOnly: true,
secure: true,
sameSite: "None",
path: "/",
maxAge: 7 * 24 * 60 * 60 * 1000,
});
これで、フロントとバックエンドが分離された環境でもリフレッシュトークンの安全な管理が実現できます 🎉