はじめに
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,
});
これで、フロントとバックエンドが分離された環境でもリフレッシュトークンの安全な管理が実現できます 🎉