0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Node.js】Cookie設定でSameSite/Secureに躓いた話

Posted at

はじめに

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日
});

ポイント

  1. secure はローカル開発環境でも true でOK(HTTPSでなくても送られる)
  2. sameSiteNone にしないとクロスサイトリクエストで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日
});

まとめ

  1. secure: truelocalhostでも送られる(最近のブラウザ仕様)
  2. 別ドメインでCookieを送るには sameSite: "None" が必須
res.cookie("refreshToken", refreshToken, {
  httpOnly: true,
  secure: true,
  sameSite: "None",
  path: "/",
  maxAge: 7 * 24 * 60 * 60 * 1000,
});

これで、フロントとバックエンドが分離された環境でもリフレッシュトークンの安全な管理が実現できます 🎉

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?