ログイン機能を作って認証情報を受け取ったあと、多くの人が最初に迷うのが「これをどこに保存するのか」です。よく候補に挙がるのがブラウザのlocalStorageと、Cookie。選び方によってセキュリティが変わります。
なお、ひとくちに「認証トークン」といっても、サーバー上のログイン状態を指すセッションID、APIにアクセスするための短命なアクセストークン、それを再発行するためのリフレッシュトークンなど、いくつか種類があり、最適な保存方法は種類や構成で変わります。この記事では、ブラウザからAPIを使う一般的なWebアプリを前提に、保存場所の違い、XSSやCSRFとの関係、迷ったときの現実的な選び方を整理します。トークンそのものの仕組みはトークン認証とはで解説しています。


localStorage と Cookie の違い
保存先としてよく使われるのが、この2つです。
- localStorage:ブラウザにデータを保存する仕組み。JavaScriptから自由に読み書きできる。
- Cookie:サーバーとやり取りする小さなデータ。HttpOnly を付けるとJavaScriptからは読めなくなり、条件に合うリクエストでブラウザが自動的に送る。
違いは、大きく「JavaScriptから読めるか」と「リクエストに自動で付くか」の2点です。ここが、後で出てくるXSSとCSRFのリスクに直結します。なお、どちらが使えるかは、APIがCookie認証に対応しているか、それとも Authorization ヘッダーにトークンを要求するかによっても変わります。
localStorage:手軽だが、XSSでトークンを盗まれうる
localStorageは扱いが簡単で、トークンを入れて必要なときに取り出す実装がすぐ書けます。ただし、JavaScriptから読めるという性質が、そのまま弱点になります。
もしサイトに、悪意のあるスクリプトを実行されてしまう脆弱性(XSS)があると、そのスクリプトはlocalStorageの中身も読めます。保存しておいたトークンを丸ごと外部へ送られ、盗まれる可能性があります。盗まれれば、攻撃者はそれを使ってあなたのユーザーになりすませます。
HttpOnly Cookie:JavaScriptから直接盗まれにくい。ただしCSRF対策が要る
一方、HttpOnly を付けたCookieは、JavaScriptから読めません。そのため、XSSがあっても、トークンそのものは直接は盗まれにくくなります。
ただし、Cookieは、Domain・Path・SameSiteなどの条件に一致するリクエストで、ブラウザが自動的に付けて送ります。この「自動で付く」性質を悪用されるのがCSRFです。攻撃者が用意した別のサイトから、あなたのサイトへリクエストを飛ばすと、ブラウザがログイン中のCookieを勝手に付けてしまい、ユーザーのブラウザを利用して、意図しない操作を実行させられることがあります。Cookieそのものの仕組みはCookieとはで整理しています。
だからCookieを使うなら、CSRF対策がセットになります。ここで、Cookieの属性は役割がそれぞれ違うので、混同しないことが大事です。
- HttpOnly:JavaScriptからCookieを直接読めなくする。
- Secure:HTTPS通信でのみCookieを送る(通信経路の保護)。CSRFを防ぐ属性ではありません。
- SameSite:クロスサイトでのCookie送信を制限する(CSRFリスクを下げる重要な防御の一つ)。Strictはクロスサイトリクエストでは送らず、Laxは一部の遷移では送り、Noneはクロスサイトでも送る(その場合Secureが必要)。
- CSRFトークンやOriginの検証:状態を変えるリクエストに対する、追加のCSRF対策。
注意したいのは、SameSiteの「site」は「オリジン」と同じではない点です。たとえば同じ example.com 配下の別サブドメインは、別オリジンでも同一サイト扱いになることがあり、SameSiteだけではすべてのCSRFを防げるとは限りません。だからSameSiteを適切に設定したうえで、状態を変える操作にはCSRFトークンなども組み合わせる、という多層で考えます。


迷うなら、「ブラウザに実トークンを持たせない」構成が有力
localStorageとCookieの比較で整理すると、盗まれにくさではCookieのほうが有利です。ただ、セキュリティを優先するなら、もう一歩踏み込んだ構成が有力です。
自社のWebアプリで迷うなら、アクセストークンなどの実トークンをブラウザに直接持たせるのではなく、サーバー側でセッションやトークンを管理し、ブラウザにはHttpOnly・Secure・適切なSameSiteを設定したセッションCookieだけを持たせる構成が有力です。ブラウザには、サーバー側のセッションを指し示すランダムなセッション識別子だけをCookieとして持たせ、アクセストークンやリフレッシュトークンはサーバー側で管理します。OAuthを利用し、バックエンドがトークン管理とAPIへの代理アクセスまで担う構成は、BFF(Backend for Frontend)と呼ばれます。
一方、SPAが Authorization ヘッダーにトークンを載せてAPIを直接叩く構成では、HttpOnly CookieのトークンはJavaScriptから読めないため、そのままヘッダーに載せられません。この場合は、APIをCookie認証に対応させる、サーバーを1枚挟む(BFF)、アクセストークンはブラウザのメモリで扱う、といったアーキテクチャの選択が必要になります。つまり、最適解はAPIや認証基盤の作りによって変わります。「Cookieに入れれば安心」と一律に決めず、構成から考えるのが正確です。
メモリ+リフレッシュトークンという形もある
もう一つ、アクセストークンはブラウザのメモリ(変数)に置き、リフレッシュトークンだけをCookieに保存する構成もあります。リフレッシュトークンをHttpOnly Cookieに置く場合は、バックエンド側の再発行エンドポイントがCookieを受け取り、アクセストークンの再発行処理を行います。
メモリに置いたトークンは、ページ更新やタブ終了で消え、永続ストレージにも残らないため、localStorageより露出する期間を短くできます。ただし、ページ上でXSSが実行されている間は、保管方法によってはメモリ内のトークンを読み取られる可能性があります。直接読み取れないよう分離していても、正規アプリと同じ方法でAPIを呼ばれたり、新しいトークンを取得されたりする可能性は残ります。メモリ保存は「露出を今の実行中に限定する」もので、XSSへの完全な防御ではありません。
この形を採るなら、リフレッシュトークンのローテーション、有効期間の上限や未使用時の失効、ログアウト時の失効処理、再発行エンドポイントのCSRF対策まで含めて設計します。
いちばん大事なのは、保存場所より「XSSを作らないこと」
ここまで保存場所の話をしてきましたが、もっと根っこにある前提があります。XSSを通さない作りにすることです。
XSSがあると、localStorageのトークンは読まれます。Cookieでトークン自体を守れても、XSSで実行されたスクリプトは、その場でログイン中のユーザーとして操作を実行できてしまいます。サーバー側にトークンを置く構成でも、ブラウザが乗っ取られること自体は残ります。つまり、保存場所をどれだけ工夫しても、XSSが残っていれば安全とは言えません。
XSS対策は、エスケープだけではありません。フレームワークの自動エスケープを無効にしない、出力先(HTML・属性・URL・JavaScript)に合ったエンコードをする、HTMLを許可する場合はサニタイズする、innerHTMLやdangerouslySetInnerHTMLのような危険な出力を避ける、外部スクリプトや依存パッケージを管理する、そして多層防御としてCSP(コンテンツセキュリティポリシー)を設定する、といった積み重ねになります。公開時に確認したいサーバ側の全体像はAIで作ったアプリのサーバは安全かも参考になります。
今日やれる、トークン保存まわりの点検
- 実トークンをlocalStorageに置いていないか。ブラウザに直接持たせない構成を検討したか。
- Cookieを使うなら、HttpOnly・Secure・適切なSameSiteが付いているか。
- 状態を変えるリクエストに、必要なCSRF対策(SameSite+必要ならCSRFトークン/Origin検証)があるか。
- 出力先に応じたエスケープ・サニタイズで、XSSを防げているか。危険な出力方法を使っていないか。
- AIに書かせた認証コードを、そのまま信用せず、保存場所・XSS・CSRFの観点で確認したか。
AIで開発すると、認証まわりも短時間で形になります。ただ、保存場所やXSS・CSRFの考え方は、AIに丸投げでは詰めきれません。動くかどうかだけでなく、安全な作りとテストで、脆弱なサイトにしないようにしましょう。
まとめ
- 「認証トークン」にはセッションID・アクセストークン・リフレッシュトークンなどがあり、最適な保存方法は種類と構成で変わる。
- localStorageはXSSで盗まれやすい。HttpOnly Cookieは直接読まれにくいが、属性の役割は別物(HttpOnly=JavaScriptからの直接読み取り防止、Secure=通信経路、SameSite=クロスサイトでのCookie送信を制限)。SecureはCSRF対策ではない。
- SameSiteだけで全CSRFは防げない。状態を変える操作にはCSRFトークンなども組み合わせる。
- 迷うなら、ブラウザに実トークンを持たせず、サーバー側で管理してHttpOnlyセッションCookieだけを持たせる構成が有力。最適解はAPI構成による。
- 最も大事なのは保存場所より、XSSを作らないこと。XSSが残れば、どこに置いても安全とは言えない。




