セッション管理の基本:ID の作り方・作り直し・Cookie の属性・タイムアウト(IPA・OWASP・NIST)

PR
セッション管理の基本:ID の作り方・作り直し・Cookie の属性・タイムアウト(IPA・OWASP・NIST)
この記事は約33分で読めます。

セッション管理は、ログインした人を、次のリクエストでも「同じ人」と見分け続ける仕組みを、盗まれにくく、なりすまされにくく作ることです。セッション(ログインしてから終わるまでの、ひとまとまりのやり取り)を見分ける鍵として、サーバーが「セッション ID」という文字列を発行し、ブラウザーが Cookie(ブラウザーが覚えておいて、次のリクエストに付けて送る小さな文字列)で返します。この ID が当てられたり、盗まれたり、決められたりすると、その人になりすませてしまいます。

この記事は、ログインのある Web アプリを作り始めた人に向けて、ID の作り方、ログイン後の作り直し、Cookie の属性、ログアウトとタイムアウトを書いたものです。扱うのは、サーバーに状態を持つ「集中型」のセッションだけです。 署名つきのトークン(JWT)との比べ方や、JWT の保存場所は、JWT とは?仕組み・セッションとの違い・保存場所の注意点に任せます。

書いてあることは、IPA・OWASP・NIST・MDN・Express・Python・curl の文書を、2026年10月21日に開いて確かめたものです。ID を変えない形と作り直す形、ログアウト、タイムアウトは、macOS 14.4.1・Python 3.12.3・curl 8.4.0 の手元で流した結果を載せています。サーバーは Python の標準ライブラリだけで書き、自分のパソコンの 127.0.0.1 でだけ待ち受けて、確かめが終わったら止めました。ブラウザーでは確かめていません。Express の例も、動かしていません。 OWASP の Cheat Sheet(開発者向けの短い手引き)は頻繁に書き換わります。この記事の「2026年10月時点」は、その日に読んだ内容です。作るときは、最新の文書を開き直してください。

この記事でわかること
  • セッション ID が当てられる・盗まれる・決められると、なぜなりすませるのか(集中型だけの話)
  • ID は自分で作らずフレームワークのものを使うこと。自作するなら、乱数の大きさと渡し方(URL に入れない)
  • ログインに成功したら ID を作り直す理由と、手元で「変えない形」「作り直す形」を比べた結果
  • Cookie の属性(Secure・HttpOnly・SameSite・Domain・Path・期限・__Host-)の組み合わせと、OWASP・NIST の書き方の違い
  • ログアウトでサーバー側のセッションも消すこと、アイドルと絶対のタイムアウト、NIST SP 800-63B-4 第 5 章の決まり
  1. セッション管理とは?なぜ大事?(2026年10月時点)
    1. 集中型だけを扱います
    2. ID が当てられる・盗まれる・決められると、なりすませる
    3. 3つの文書と、この記事の読み方
  2. セッション ID はどう作る?どう渡す?
    1. 自分で作らず、フレームワークのものを使う
    2. 自分で作るなら:乱数の大きさ
    3. ID は URL に入れず、Cookie で受け取る
    4. 作っていない ID は受け付けない
  3. ログインに成功したら ID を作り直す(セッション固定化への対策)
    1. 固定化とは?
    2. 作り直す場面と、書いてあること
    3. 手元で確かめる:ID を変えない形と作り直す形
  4. Cookie の属性はどう組み合わせる?
    1. 属性の一覧:OWASP と NIST
    2. Secure:HTTPS だけで送る
    3. Domain は付けず、Path は守りにならない
    4. 接頭辞で Cookie の条件を固定する
    5. HttpOnly と SameSite
    6. 期限(Expires・Max-Age)
  5. ログアウトとタイムアウトは、サーバー側で終わらせる
    1. ログアウトは、Cookie を消すだけでは足りない
    2. 手元で確かめる:Cookie だけ消す形とサーバー側も消す形
    3. ブラウザーを閉じても、ログアウトにはならない
    4. アイドルと絶対のタイムアウト
    5. 手元で確かめる:時間で切れる
  6. NIST SP 800-63B-4 第 5 章は何を決めている?
    1. SHALL と SHOULD を見分ける
    2. 再認証の時間:AAL ごとの数
  7. Express(express-session)ではどう書く?(公式の文書の範囲)
    1. 既定の設定で気をつけること
    2. ログインとログアウトの書き方
  8. よくある質問
    1. Secure の Cookie は、http では送られませんか?
    2. express-session の regenerate は、前の ID を消しますか?
    3. セッション管理の不備で起きた事件の例はありますか?
  9. まとめ
  10. 参考資料

セッション管理とは?なぜ大事?(2026年10月時点)

HTTP は、1回ごとのやり取りを覚えていません。そのため、ログインした状態を続けるには、セッションの仕組みが要ります。ログインのあとの「この人は alice さん」という判断は、リクエストのたびに、送られてきた ID から決め直すことになります。だから、次に書くとおり、ID はとても重い鍵になります。

集中型だけを扱います

MDN は、ログインを保つ方法を2つ挙げています。サーバーが状態を持ち、クライアントは ID だけを持つ「集中型」と、署名つきのトークン(ふつうは JWT)に状態を持たせる「分散型」です。この記事は集中型だけを扱います。2つの比べ方はJWT の記事にまとめてあります。

集中型は、サーバーの状態を消せば、その ID を無効にできます。「ログアウト」の節は、この前提の上にあります。

ID が当てられる・盗まれる・決められると、なりすませる

OWASP は、ログイン済みのセッションができると、セッション ID は、一時的に「そのアプリで使ういちばん強い認証の方法」(パスワード・ワンタイムパスワード・証明書・生体など)と同じ重みになる、と書いています。セッション ID が漏れる、盗まれる、予測される、総当たりされる、固定されると、セッションの乗っ取り(hijacking)になり、被害者に完全になりすませる、とも書いています。

IPA(情報処理推進機構)の「安全なウェブサイトの作り方」は、ID の発行や管理に不備があると、ログイン中の利用者の ID を取られ、その利用者になりすまして本人に許された操作をされると書いています。この攻撃を「セッション・ハイジャック」と呼びます。例として、不正な送金、意図しない購入や退会、設定やパスワードの変更、非公開の個人情報やウェブメールの閲覧が挙がっています。

MDN は、乗っ取りの入口を3つに分けています。通り道で盗まれる(守りは TLS)、予測・推測・総当たりをされる(守りは、十分に長く、ランダムな ID)、ブラウザーの中の悪いコードに JavaScript から ID を読まれる(守りは、ふつうの XSS の守り)です。

もう1つ、セッション ID の固定化(Session Fixation)という攻撃があります。守り方と手元の確かめは、「ログインに成功したら ID を作り直す」の節に書きます。

OWASP Top 10:2025 の A07(Authentication Failures。日本語版の題は「認証の不備」)にも、セッションの扱いが入っています。ID を URL や hidden フィールド(画面に出ない入力欄)に出す、ログイン後も同じ ID を使い続ける、ログアウトや無操作のあとに無効にしない、が当てはまる条件として挙がっています。

3つの文書と、この記事の読み方

文書どういうもの
IPA「安全なウェブサイトの作り方」1.4 セッション管理の不備日本の手引き。改訂第7版(2021年3月31日 第4刷)。開いたページに日付の表示は見つかりませんでした
OWASP Session Management Cheat Sheet開発者向けの手引き。版や日付の表示は無く、GitHub の最終コミットは2026年10月4日。must(しなければならない)・should(するとよい)で書く
NIST SP 800-63B-4 第 5 章米国の政府の情報システム向けの指針(2025年7月の最終版)のうち、従うべき決まり(規範)の章。SHALL(必ず守る)・SHOULD(勧める)・MAY(してよい)を区別する

MDN の Session management(最終更新は2026年2月24日)も使います。文書によって数や強さが違うところは、揃えずに、出どころを分けて書きます。

ほかの話は、ほかの記事に任せます。XSS と CSRF(ほかのサイトから、ログイン中の利用者の権限で操作をさせる攻撃)はXSS と CSRF の違いとは?、認可は認証と認可の違いとは?、パスワードの保存はパスワードの正しい受け取り方と保存のしかた、ログインの試行回数の制限はレートリミットとは?、点検表の全体はWeb 開発のセキュリティの基本にあります。

セッション ID はどう作る?どう渡す?

自分で作らず、フレームワークのものを使う

IPA は、セッション管理の仕組みを持つ製品を使っている限り、自前で ID を作る必要はなく、自前で仕組みを作ろうとしないことを勧めています。OWASP も組み込みのセッション管理を、MDN も定評のあるフレームワークやライブラリを勧めています。ただし OWASP は、過去に穴もあったので、最新の版を使い、既定の設定を見直すよう書いています。例えば ID の名前です。PHPSESSID(PHP)や JSESSIONID(J2EE)のような既定の名前からは、使っている技術が分かるので、id のような一般的な名前に変えるよう勧めています。

自分で作るなら:乱数の大きさ

出どころ書いていること
IPA時刻などから単純なアルゴリズムで作ると、第三者に予測される。暗号論的擬似乱数生成器を使うなどして、予測しにくくする
OWASP総当たりを防ぐため、少なくとも 64 ビットのエントロピー(must)。強い CSPRNG で作る(must)。自分で作るなら、CSPRNG で 128 ビット以上にして、ID ごとに一意にする
NIST SP 800-63B-4セッションの秘密は、認証のときかその直後に、認められた乱数生成器から作り、64 ビット以上にする(SHALL を並べた一覧の中の2項目)

CSPRNG(暗号論的擬似乱数生成器)は、次の値を予測できないように作られた乱数の作り方です。OWASP は、大事なのは長さよりエントロピー(当てにくさの大きさ)だと書き、一部が固定だと実際の大きさは減ると注意しています(16 進 16 文字の半分が固定なら、32 ビット)。

OWASP は、64 ビットの ID を当てるのに、毎秒1万回試し、有効なセッションが10万ある、という条件で約585年かかると見積もっています。条件が変われば数も変わるので、「最低でも 64 ビット」の根拠として読んでください。

64 ビットと 128 ビットの2つの数は、OWASP の同じページに並んでいます。 最低の線が 64 ビット(OWASP・NIST)で、自分で作るなら OWASP は 128 ビット以上、と読みます。まずはフレームワークの ID を使います。

Python なら標準ライブラリの secrets を使います(シミュレーション向けの random は使いません)。secrets.token_urlsafe(32) は、32 バイト(256 ビット)の乱数を、URL に使える Base64 の文字列にします。Python の文書は、secrets の典型的な使い方には、32 バイトで足りると考えられている(2015年時点)と書いています。手元の Python 3.12.3 で文字数を数えました。

$ python3 -c "import secrets; print(len(secrets.token_urlsafe(32)))"
43

43 文字で、OWASP の 64 ビットも 128 ビットも、NIST の 64 ビットも上回ります。

OWASP は、ID の中身に意味を持たせず、利用者の情報や個人情報を入れないよう書いています。意味(どの利用者か、どんな権限か)はサーバー側に置きます。認可を毎回確かめることは認証と認可の記事に書きました。

ID は URL に入れず、Cookie で受け取る

IPA は、ID を URL のパラメーターに入れないよう書いています。Referer(どのページから来たかを伝えるヘッダー)で URL ごとリンク先に送られ、手に入れられると乗っ取られるからです。Cookie を拒む利用者には ID を URL に自動で入れる製品があるので、その機能を止めることも検討するよう書いています。OWASP も、URL に入れると、リンク・ログ・履歴・ブックマーク・Referer・検索エンジンから漏れ、固定化も助けると書き、Cookie だけに絞って、URL のパラメーターなど別の道から来た ID は受け付けないよう勧めています。既定が Cookie でも他の経路を受け付けていることがあるので、テストで確かめます。

IPA の書き方には食い違いがあります。 IPA は Cookie か POST の hidden パラメーターで渡すよう勧めますが、同じページの脚注は、hidden で受け渡す実装を固定化の入口の1つに挙げます。OWASP は Cookie だけに絞り、OWASP Top 10:2025 の A07 の英語版は hidden フィールドを安全でない場所の例に挙げます。この記事は Cookie に寄せます。

作っていない ID は受け付けない

OWASP は、受け取った ID も、ほかの入力と同じく信用せずに確かめるよう書いています(確かめないと、SQL インジェクションや XSS に使われるおそれがあります)。

また OWASP は、利用者が出した見知らぬ ID でもセッションを作る方式(permissive)と、サーバーが作った ID しか受け付けない方式(strict)を挙げ、作っていない ID は受け付けず、来たら新しい ID を渡すよう勧めています。手元のサーバーは strict で、知らない ID には reason=unknown-id と答えます。

ログインに成功したら ID を作り直す(セッション固定化への対策)

固定化とは?

固定化は、MDN の言い方では、攻撃する側が ID の値を決め、サイトにその値を狙った利用者の ID として使わせる攻撃です。ID を盗む必要はなく、最初から知っています。IPA も、用意した ID を利用者に送り込み、気づかずにログインさせると起こる、と書いています。送り込む方法は書きません。

作り直す場面と、書いてあること

出どころ書いていること
IPA(4-(iv)-a)ログインの前に ID を出し、ログイン後もそのまま使う実装は、固定化に弱い場合がある。ログインに成功した時点で新しいセッションを始め(新しい ID にし)、既存の ID は無効にする
MDNサーバーは、利用者がサインインするたびに、新しい ID を必ず作り、既存の値を無効にする。これがいちばんの守り
OWASP権限の段階が変わったら、ID を作り直す(must)。いちばん多いのはログイン(匿名からログイン済みへ)。ほかにパスワードの変更・権限の変更・一般から管理者への切り替え。前の ID は捨てる
NIST SP 800-63B-4セッションの秘密は、認証の出来事に直接応じて、サービスの側(session host)が作る(SHALL)。「作り直す」という語は使っていない

なぜ作り直しが要るのか。OWASP は、今のアプリは、ログインの前(匿名の利用者の言語設定の記憶など)にも後にもセッションを持ちうる、と書いています。ログイン前に出した ID がログイン後も使えると、固定化に使われます。OWASP Top 10:2025 の A07 の防ぎ方も、ログイン後に新しいランダムな ID を作る、です。MDN は Cookie のガイドでも、認証のたびに、すでにある Cookie も含めて、作り直して送り直すよう書いています。

手元で確かめる:ID を変えない形と作り直す形

これは仕組みを見せるための説明用のサーバーです。 Python の標準ライブラリだけで書き、セッションは辞書に入れ、ID は secrets.token_urlsafe(32) で作ります。パスワードの確かめは省き(本物はパスワードの記事の形)、CSRF の守りも入れていません。ID はそのまま辞書のキーにしています(OWASP は、読み出しの漏えいまで考えるなら、ID と検証用の秘密を分け、秘密のハッシュを保存する形を挙げています)。やっているのは、「ログイン前に出た ID が、ログイン後も通るか」を見ることだけです。他人に ID を送り込む手順は書きません。ログインの処理は、起動の引数で2つの形に切り替えます。

if self.path == "/login":
    user = form.get("user", [""])[0]
    # 説明用: パスワードの確かめは省いている
    if a.mode == "rotate":
        if sid is not None:
            del SESSIONS[sid]  # 前の ID はサーバー側で消す
        sid = new_session()
        SESSIONS[sid]["user"] = user
        return self.reply(200, f"logged in as {user} (new id)", cookie_header(sid))
    if s is None:
        sid = new_session()
        SESSIONS[sid]["user"] = user
        return self.reply(200, f"logged in as {user}", cookie_header(sid))
    s["user"] = user  # よくない形: ログイン前の ID のまま、ログイン済みにする
    return self.reply(200, f"logged in as {user} (same id)")

a.mode が rotate のときが「ID を作り直す形」、そうでないときが「ID を変えない形」です。

まずID を変えない形です。ログイン前に出た ID の入れ物(jar)を before.jar に写しておき、ログイン後に、その写しで /me(ログイン中の利用者を答える入口)を開きます。ID の値は <ID-1> のように置き換え、状態の行と見出しの行(Server・Date など)は省きました。

$ curl -s -i -c a.jar http://127.0.0.1:8701/
Set-Cookie: sid=<ID-1>; Path=/; HttpOnly; SameSite=Lax
anonymous (new session; reason=no-cookie)
# ここで a.jar を before.jar に写した(ログイン前の ID の写し)
$ curl -s -i -b a.jar -c a.jar -d user=alice http://127.0.0.1:8701/login
(Set-Cookie の行は無い)
logged in as alice (same id)
$ curl -s -b before.jar http://127.0.0.1:8701/me
logged in as alice

ログインの応答に Set-Cookie が無く、ログイン前の写し(<ID-1>)でも、ログイン済みとして通りました。IPA が「脆弱な場合がある」と書く実装の形です。

次に、ログインで作り直す形です。同じ手順で流します。

$ curl -s -i -c b.jar http://127.0.0.1:8702/
Set-Cookie: sid=<ID-2>; Path=/; HttpOnly; SameSite=Lax
anonymous (new session; reason=no-cookie)
# ここで b.jar を before.jar に写した
$ curl -s -i -b b.jar -c b.jar -d user=alice http://127.0.0.1:8702/login
Set-Cookie: sid=<ID-3>; Path=/; HttpOnly; SameSite=Lax
logged in as alice (new id)
$ curl -s -b b.jar http://127.0.0.1:8702/me
logged in as alice
$ curl -s -b before.jar http://127.0.0.1:8702/me
not logged in (reason=unknown-id)

ログインの応答で ID が <ID-2> から <ID-3> に変わり、前の <ID-2> は unknown-id になりました。最後の行の本文は、サーバーが 401 で返す文です。

Cookie の属性はどう組み合わせる?

セッション ID は、Set-Cookie ヘッダーに属性を付けて、Cookie で送ります。置き場は Cookie です。OWASP は localStorage・sessionStorage に入れないよう書き、MDN は Cookie を勧め、NIST も Local Storage のような安全でない場所に置かないべき(SHOULD NOT)と書いています(理由はJWT の記事)。

属性の一覧:OWASP と NIST

属性OWASPNIST SP 800-63B-4(5.1.1)
Secure(HTTPS のときだけ送る)必須(mandatory)HTTPS のセッションでだけ使えるようにする(SHALL)
HttpOnly(JavaScript から読めなくする)必須(mandatory)読めないようにする(SHOULD)
SameSite(ほかのサイトからのリクエストに付けるか)Strict(推奨)か Lax を明示する(must)。ブラウザーの既定に頼らないLax か Strict(SHOULD)
Domain付けない(送り元のサーバーだけに送る)使えるホスト名とパスを、実用上の最小にする(SHALL)
Pathできるだけ絞る同上(SHALL)
名前の接頭辞 __Host-セッション ID に勧める__Host- を付け、Path=/ にする(SHOULD)
期限(Expires・Max-Age)保つ必要がなければ永続的でない Cookie。ブラウザーを閉じることに頼らない有効期間かその直後に切れるようにする(SHOULD)。ただしタイムアウトの実施に頼ってはならない(SHALL NOT)
中身意味を持たせない(must)意味の無い文字列(例: セッション ID)だけ(SHOULD)。平文の個人情報を入れない(SHALL NOT)

強さが出どころで違うので、列を分けました(NIST の大文字は原文のままです)。IPA の手引きは、この中で secure 属性だけを根本的解決(4-(iii))に挙げ、開いた 1.4 のページに HttpOnly と SameSite の語はありません。

OWASP の例は、次の形です。

Set-Cookie: __Host-SessionID=<value>; Secure; HttpOnly; SameSite=Strict; Path=/

Secure:HTTPS だけで送る

IPA は、HTTPS で使う Cookie には secure 属性を必ず付けるよう書いています。付けないと、HTTPS で出した Cookie が暗号化されない HTTP でも使われ、盗聴で取られるおそれがあるからです。OWASP は、サーバーを HTTPS だけにして 80 番を閉じていても、Secure が無ければ、ブラウザーをだまして HTTP で ID を送らせられる、と書き、セッション全体を HTTPS にすること、HSTS(ブラウザーに HTTPS で接続し続けるよう指示する仕組み)を入れることも挙げています。TLS は、ID の予測・総当たり・固定化は防ぎません。

MDN は、Secure の Cookie は HTTPS のリクエストのときだけ送られ、暗号化されない HTTP では送られない、と書いています。例外は localhost で、Safari はこの例外に対応していません。http: のサイトは、Secure の Cookie を設定できません。Secure を付けても、ディスクや(HttpOnly が無ければ)JavaScript からは読めます。

この「HTTP では送られない」を、手元の curl で見ようとしました。Secure を付けた Cookie を出すサーバーに、接続先の名前を変えて http:// で行った結果です。

接続先(すべて http)Secure 付きの Cookie__Host-sid の名前の Cookie
http://127.0.0.1保存して、次のリクエストに付けて送った同じ
http://localhost(名前を 127.0.0.1 につなぐ指定で)同じ同じ
http://example.test(同じ指定で 127.0.0.1 につないだ)保存しなかった。次のリクエストには Cookie が付かなかった保存しなかった

対照として、Secure を付けないサーバーでは、http://example.test でも Cookie を保存しました。

手元の curl 8.4.0 は、127.0.0.1 と localhost では、Secure の Cookie を http でも保存して送りました。 curl の文書は http://localhost を安全な文脈と見なすと書いていますが、127.0.0.1 は、読んだ範囲に書かれていません。ですから、この結果から「http では Secure の Cookie は送られない」とは言えません。それは MDN の文として読んでください。ブラウザーでは試していません。

Domain は付けず、Path は守りにならない

OWASP は、Domain は付けず(送り元のサーバーだけに送る)、Path はできるだけ絞るよう書いています。example.com のような広い Domain は、同じドメインの別のホストの穴から ID を取られる(OWASP の例は www.example.com の穴から secure.example.com の ID)ことがあるからです。MDN も、Domain を付けると、そのドメインとすべてのサブドメインに送られ、付けなければ設定したホストだけ(host-only)で、そのほうが狭いと書いています。

Path は、書き方がぶつかって見えます。 OWASP は「できるだけ絞る」と書く一方、__Host- を勧め、__Host- は Path=/ が必須です(NIST も、最小のパス(SHALL)と、__Host- と Path=/(SHOULD)を並べます)。MDN は、Path は送る Cookie をサイトの部分で分けるためのもので、別のパスから読まれるのを防がず、セキュリティの仕組みではないと書いています。この記事は、Domain は付けない、__Host- を使うなら Path=/、Path で守れるとは考えない、と読みます。OWASP は、同じホストで別のアプリを動かさないことも勧めています。

接頭辞で Cookie の条件を固定する

__Host- は、Cookie の名前の頭に付ける接頭辞(先頭の決まった文字列)です。MDN によると、__Host- で始まる名前の Cookie は、Secure を付け、HTTPS のページから設定し、Domain を付けず、Path=/ にする決まりで、条件に合わないとブラウザーに拒まれます。合えば、設定したホストだけに送られ、同じホストの別のパスから上書きされません。サブドメインのアプリが Domain 付きで設定した Cookie が固定化に悪用されうる、という MDN の指摘への、多層の守りの1枚です。MDN の例は次のとおりです。

Set-Cookie結果(MDN)
__Host-ID=123; Secure; Path=/受け入れられる(HTTPS から設定したとき)
__Host-id=1; SecurePath=/ が無いので拒まれる
__Host-id=1; Secure; Path=/; Domain=example.comDomain があるので拒まれる

OWASP は、__Host- をセッション ID に勧めています(サブドメインからの偽造と HTTPS の格下げを防ぐ)。注意が2点あります。

  • 対応していないブラウザーでは、この保証はありません(MDN)。接頭辞つきでも受け入れられます
  • サーバーは、接頭辞を含む名前のまま確かめます。 ブラウザーは接頭辞を外さずに送ります。手元のサーバーでも(curl で送った場合)__Host-sid の名前のまま Cookie が届きました

HttpOnly と SameSite

HttpOnly は、Cookie を JavaScript から読めなくします。OWASP は、XSS で ID を盗まれるのを防ぐのに必須と書いています。ただし XSS と CSRF を組み合わされると、ブラウザーは Cookie を付けて送るので、守れるのは Cookie の秘密だけです(MDN も、fetch() などのリクエストには付くと書いています)。詳しくはXSS と CSRF の記事にあります。

SameSite(ほかのサイトから出たリクエストに Cookie を付けるか)の値と、CSRF の守りの代わりにならないことは、同じ記事に書きました。足すのは1点で、OWASP は、セッションの Cookie に Strict(推奨)か Lax を明示し、ブラウザーの既定値(ブラウザーと版で違う)に頼らないよう書いています。付けないときの既定の値は、文書によって言い方が違うので、この記事では書きません。

期限(Expires・Max-Age)

Max-Age か Expires があると永続的な Cookie になり、期限までディスクに保存されます。OWASP は、保つ必要がなければ永続的でない Cookie を使うよう書きますが、ブラウザーを閉じればセッションが終わるとは頼れません(セッションを復元するブラウザーは Cookie を残せます)。IPA も、期限を省いてブラウザーの終了で消す方法は、閉じずに使い続けると消えないと書き、期限を短くすることを挙げています。

言い方には少し違いがあります。MDN の Session management(2026年2月24日更新)は、OWASP は付けないよう勧めるが、多くのサイトは永続的な Cookie を使う、と書いています。NIST は、期限をセッションの有効期間かその直後にする(SHOULD)としつつ、タイムアウトの実施には頼ってはならない(SHALL NOT)と書きます。一致しているのは、時間切れを Cookie の期限に任せず、サーバー側で切ることです。 期限を付けるか付けないかは、言い切りません。

ログアウトとタイムアウトは、サーバー側で終わらせる

ログアウトは、Cookie を消すだけでは足りない

OWASP は、セッションが終わるとき(期限切れでもログアウトでも)、クライアントとサーバーの両方で無効にするよう書いています。安全の点で必須なのはサーバー側です。 クライアント側は、空の値と過去の Expires(または Max-Age)の Cookie を返して消し、サーバー側は、フレームワークの関数(PHP なら session_destroy() など)でセッションを無効にします。Cookie を消すだけでは、盗まれた ID の写しは無効になりません。

MDN は、Cookie の消し方を、同じ名前・パス・ドメインで設定し直し、Expires を過去の日付にするか Max-Age を 0 以下にする、と書いています。例は Set-Cookie: id=a3fWa; Max-Age=0 です。

ログアウトのボタンは、OWASP は、どのページからも届く形で用意し(must)、少なくともサーバー側でセッションを無効にする(must)と書きます。NIST は、ログオフの手段をすぐ使える形で用意すべき(SHOULD)です。強さが違うので、出どころごとに書きました。

手元で確かめる:Cookie だけ消す形とサーバー側も消す形

先ほどの説明用サーバーに、ログアウトの入口を2つ用意しました。

if self.path == "/logout":  # サーバー側も消す
    if sid is not None:
        del SESSIONS[sid]
    return self.reply(200, "logged out (server-side deleted)", cookie_header("", 0))
if self.path == "/logout-cookie-only":  # よくない形: Cookie を消させるだけ
    return self.reply(200, "logged out (cookie only)", cookie_header("", 0))

どちらも、返す Set-Cookie は同じ(値が空で、Max-Age=0)です。違うのは、サーバーの辞書から ID を消すかどうかだけです。ログアウトの前に、ログイン済みの入れ物を copy.jar に写しておき、ログアウトのあとで、その写しを使って /me を開きます。

# ここで b.jar(ログイン済み <ID-3>)を copy.jar に写した
$ curl -s -i -b b.jar -c b.jar -X POST http://127.0.0.1:8702/logout-cookie-only
Set-Cookie: sid=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0
logged out (cookie only)
$ grep -v '^# ' b.jar | grep .
(何も出ない)
$ curl -s -b copy.jar http://127.0.0.1:8702/me
logged in as alice

ブラウザー側の入れ物(b.jar)からは行が消えましたが、写しの ID は、まだログイン済みとして通りました。次は、ログインし直して、サーバー側も消す入口で試します。

$ curl -s -c b.jar -d user=alice http://127.0.0.1:8702/login
logged in as alice (new id)
# ここで b.jar を copy.jar に写した
$ curl -s -i -b b.jar -c b.jar -X POST http://127.0.0.1:8702/logout
Set-Cookie: sid=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0
logged out (server-side deleted)
$ curl -s -b copy.jar http://127.0.0.1:8702/me
not logged in (reason=unknown-id)

写しの ID は通らなくなりました。OWASP の「Cookie を消すだけでは、盗まれた ID の写しは無効にならない」を、手元で見た形です。

ブラウザーを閉じても、ログアウトにはならない

OWASP は、ブラウザーを閉じたことでログアウトさせるのは確実にはできない(モバイルでは beforeunload が出ないことがある)ので、サーバー側のタイムアウトと明示のログアウトを使うよう書いています。OWASP Top 10:2025 の A07 のシナリオは、共用の PC で「ログアウト」を押さずにタブを閉じて離れた人のセッションが、タイムアウトが正しくないと残る、というものです。

アイドルと絶対のタイムアウト

OWASP は、すべてのセッションに期限を決めるのは必須(mandatory)で、短いほど攻撃に使える時間が減る、値は目的に合わせて安全と使いやすさの釣り合いで決める、と書いています。

名前切れるときOWASP の書き方
アイドル(無操作)のタイムアウト最後のリクエストから、決めた時間が過ぎたときすべてのセッションに入れるべき
絶対のタイムアウト操作の有無にかかわらず、セッションを作ってから決めた時間が過ぎたときすべてのセッションに入れるべき。切れたら、もう一度ログインさせる
更新のタイムアウト途中で、ID だけを自動で作り直す追加で入れる方法もある

アイドルだけでは足りない理由を、OWASP はこう書いています。乗っ取られたあとは、攻撃する側が、時々リクエストを出せばセッションを保てるので、アイドルのタイムアウトは攻撃する側の動きを限れません。絶対のタイムアウトは、乗っ取ったセッションを使える時間を限ります。

どのタイムアウトも、サーバー側で数えます。 OWASP は、クライアントの値で数えると攻撃する側に延ばされうると書き、MDN も、期限切れはサーバーで行い、安全の点で大事なのはサーバーの状態だと書いています。

手元で確かめる:時間で切れる

説明用サーバーの時間を、説明のために極端に短くして流しました。秒数は勧める値ではありません。 待つのは確かめの台本の中の time.sleep です。サーバーは、リクエストのたびに「最後に来た時刻」を更新し、アイドルは「今 − 最後」、絶対は「今 − 作った時刻」で数えます。

アイドル 2 秒の設定で、ログイン直後の /me は通り、3 秒待つと切れました。Cookie は入れ物に残ったままで、期限はサーバーが見ています。

$ curl -s -b d.jar http://127.0.0.1:8704/me
logged in as alice
(3 秒待った)
$ curl -s -b d.jar http://127.0.0.1:8704/me
not logged in (reason=idle-timeout)

次は、絶対 5 秒(アイドル 2 秒)の設定で、1.5 秒ごとに /me を開き続けました。ログインからの時間は、約 1.5・3.0・4.5 秒のときは通り、約 6.0 秒のときに切れました。

(1.5 秒待った。ログインから約 4.5 秒)
$ curl -s -b d.jar http://127.0.0.1:8704/me
logged in as alice
(1.5 秒待った。ログインから約 6.0 秒)
$ curl -s -b d.jar http://127.0.0.1:8704/me
not logged in (reason=absolute-timeout)

使い続けていても、絶対の時間が来ると切れます。切れたあとは、もう一度ログインして、新しい ID をもらいます。

NIST SP 800-63B-4 第 5 章は何を決めている?

NIST SP 800-63B-4 は、2025年7月に出た最終版で、米国の政府の情報システム向けの指針です。日本の法律の決まりではありません。第 5 章は「Session Management」で、従うべき決まり(規範)の章です。5.1 Session Bindings、5.1.1 Browser Cookies、5.1.2 Access Tokens、5.2 Reauthentication、5.3 Session Monitoring の節があり、5.3 はこの記事では扱いません。

大文字の SHALL・SHALL NOT は「必ず守る」、SHOULD・SHOULD NOT は「勧める(必須ではない)」、MAY は「してよい」です(詳しくはパスワードの記事)。RP は認証を使ってサービスを出す側、AAL は認証の強さの段階(1〜3)のことです。

SHALL と SHOULD を見分ける

5.1 の決まりを、強さごとに並べます(Cookie の7項目は前の表のとおりです)。

決まり強さ
セッションの秘密は、認証のときかその直後に作る。認められた乱数生成器から作り、64 ビット以上にするSHALL
セッションの秘密は、認証の出来事に直接応じて、サービスの側(session host)が作るSHALL
ログアウトしたら、利用者の側のソフト(ブラウザーなど。NIST の言う session subject)が、セッションの秘密を消すか無効にするSHALL
それに加えて、利用者の端末で、ログアウト時や期限切れのときに秘密を消すSHOULD
秘密を、HTML5 の Local Storage のような安全でない場所に置かないSHOULD NOT
認証のあと、https から http に落とさないSHALL NOT
POST・PUT の中身にセッションの識別子を入れ、RP がそれを確かめて CSRF を防ぐSHALL
ログオフの手段を、すぐ使える形で用意するSHOULD
「このブラウザーを覚える」のような Cookie を、認証の代わりに使わない(AAL2 で無操作だけ切れたときの再認証は例外)SHALL NOT

CSRF の行は、XSS と CSRF の記事の守り方(トークン)と同じ向きです。

5.1.2 のアクセストークン(OAuth などのトークン)は、あることを、ほかの手がかり無しに「利用者がその場にいる」しるしと見なしてはいけません(SHALL NOT)。ログインのセッションが終わったあとも、長く有効でありうるからです。トークンの話はJWT の記事に任せます。

再認証の時間:AAL ごとの数

5.2 は、利用者がまだそこにいるかを確かめるため、定期的に再認証を行う(SHALL)と書いています。タイムアウトは、全体(認証か前回の再認証からの時間。overall)と無操作(inactivity)の2種類で、どちらかが切れたらセッションを終えます(SHALL)。操作があれば無操作の時計を戻し、再認証に成功したら両方を戻します(SHALL)。この記事では、NIST の全体のタイムアウトを、OWASP の絶対のタイムアウトとおおよそ同じ役目のものとして並べます(どちらの文書にも、相手の言葉は出てきません)。

数は、AAL ごとに決まっています。OWASP の目安と並べると、次のようになります。

出どころ無操作(アイドル)全体(絶対)
OWASP の目安(「よくある範囲」)価値の高いアプリで 2〜5 分、リスクの低いアプリで 15〜30 分使い方による。一日中使う事務の人なら 4〜8 時間
NIST AAL1必須ではない(MAY)決める(SHALL)。30 日以内(SHOULD)
NIST AAL21 時間以内(SHOULD)決める(SHALL)。24 時間以内(SHOULD)
NIST AAL315 分以内(SHOULD)12 時間以内(SHALL)

NIST が SHALL にしているのは、「全体のタイムアウトを決めること」と「AAL3 の全体 12 時間以内」だけです。 ほかの数は SHOULD なので、「NIST は24時間と決めている」とは言えません。OWASP の数は「よくある範囲」の目安です。前提(OWASP は一般のアプリの目安、NIST は米国の政府の AAL ごとの指針)が違うので、「どれが正しい」とはまとめません。

Express(express-session)ではどう書く?(公式の文書の範囲)

Node.js の Express にセッションを足す部品が express-session です。この節は、公式の文書に書いてあることだけを書きます。動かしていません。 文書の版は v1.19.0 で、npm の最新も 1.19.0(2026年1月22日公開)でした。

既定の設定で気をつけること

  • データはサーバー側に保存し、Cookie に入るのは ID だけです
  • Cookie の既定は { path: '/', httpOnly: true, secure: false, maxAge: null } です。Secure は付かず、SameSite も付きません(cookie.sameSite の既定は false)
  • secure: true は勧められていますが、HTTPS が要り、HTTP で開くと Cookie は設定されません。プロキシの後ろなら trust proxy を設定します(文書の例は app.set('trust proxy', 1);)
  • 名前の既定は connect.sid です。OWASP の勧めのとおり、変えます
  • 既定の保存先 MemoryStore は本番向けではありません(メモリーが漏れ、1つのプロセスを越えて広げられない、と文書にあります)
  • ID には HMAC-256 で署名し、secret には少なくとも 32 バイトのエントロピーを、と文書は書いています
  • rolling: true は、毎回の応答で Cookie の期限を maxAge に戻します(既定は false)。これは Cookie の期限の話で、 NIST は Cookie の期限にタイムアウトを頼ってはならないと書いています。サーバー側のストアの期限は、文書の範囲外です
// session() に渡す設定の見本(公式の文書にある設定名を並べたもの。動かしていません)
{
  name: 'id',        // 既定は connect.sid
  secret: '...',     // 署名用の秘密。少なくとも 32 バイトのエントロピーを
  cookie: {
    secure: true,    // HTTPS が要る
    httpOnly: true,  // 既定も true
    sameSite: 'lax', // 既定は false(付けない)
  },
}

ログインとログアウトの書き方

req.session.regenerate(callback) は、セッションを作り直します。終わると、新しい SID(セッション ID)と Session が req.session に入ります。req.session.destroy(callback) は、セッションを壊し、req.session を外します。

公式の文書の「User login」の例は、ログインの処理で、固定化への対策として regenerate を呼んでから利用者の情報を入れ、save で保存してからリダイレクトします(保存より先に次のページが読み込まれないようにするため)。ログアウトでは、セッションから利用者を外して save し、もう一度 regenerate します。例のコードは、公式の文書で読んでください。

よくある質問

Secure の Cookie は、http では送られませんか?

MDN は、Secure の Cookie は暗号化されない HTTP では送られない(localhost は例外。Safari はこの例外に対応していない)と書いています。手元の curl 8.4.0 は、http://127.0.0.1 と http://localhost では送り、http://example.test では保存しませんでした。ブラウザーでは確かめていません。

express-session の regenerate は、前の ID を消しますか?

公式の文書の regenerate の説明は、終わると新しい SID と Session が req.session に入る、とだけ書いていて、前の SID の扱いは書いていません。この記事では動かしていないので、確かめていません。自分のアプリで、ログイン前の ID の写しでログイン済みの入口を開いて、通らないことを確かめてください。

セッション管理の不備で起きた事件の例はありますか?

この記事では、事件の例を探していません。IPA の手引きは、セッション管理の不備を、ログイン機能を持つウェブサイト全般に注意が必要な問題と書き、決済などの重要な処理をするサイトは、被害が大きくなるので特に注意が必要だと書いています。

自分のアプリで確かめること(チェックリスト)
  • セッション ID は、フレームワークのものを使っているか。その既定の名前と設定を見直したか(→「自分で作らず、フレームワークのものを使う」)
  • 自分で作るなら、CSPRNG で十分な大きさ(OWASP は 128 ビット以上)にし、中身に意味を持たせていないか(→「自分で作るなら:乱数の大きさ」)
  • ID を URL に入れず、Cookie だけで受け取り、作っていない ID は受け付けていないか(→「ID は URL に入れず、Cookie で受け取る」)
  • ログインに成功したら、新しい ID を作り、前の ID をサーバー側で無効にしているか(→「作り直す場面と、書いてあること」)
  • ログイン前の ID の写しで、ログイン済みの入口を開いて、通らないことを確かめたか(→「手元で確かめる:ID を変えない形と作り直す形」)
  • Cookie に Secure・HttpOnly・SameSite を明示し、Domain は付けず、可能なら __Host- を使っているか(→「属性の一覧:OWASP と NIST」)
  • ログアウトで、Cookie を消すだけでなく、サーバー側のセッションも消しているか(→「ログアウトは、Cookie を消すだけでは足りない」)
  • アイドルと絶対のタイムアウトを、サーバー側で数えているか(→「アイドルと絶対のタイムアウト」)

まとめ

  • セッション管理は、ログイン後の「同じ人」を ID で見分ける仕組み。ID が当てられる・盗まれる・決められると、なりすませる。この記事は集中型だけを扱った
  • ID は、自分で作らず、フレームワークのものを使う。自作するなら CSPRNG で。最低の線は 64 ビット(OWASP・NIST)で、OWASP は自作なら 128 ビット以上。URL には入れず、Cookie で受け取る
  • ログインに成功したら、ID を作り直す。手元では、変えない形はログイン前の写しが通り、作り直す形は通らなかった
  • Cookie は、Secure・HttpOnly・SameSite を明示し、Domain は付けない。OWASP は多くを必須(must)とし、NIST は Secure 相当と、使えるホスト名・パスを最小にすることを SHALL、HttpOnly・SameSite・__Host- を SHOULD としている。Path は守りにならない
  • ログアウトは、サーバー側のセッションも消す。手元では、Cookie だけ消す形は写しが通り、サーバー側も消す形は通らなかった
  • 時間切れは、アイドルと絶対の2つを、サーバー側で数える。数は、OWASP の目安と NIST の AAL ごとの数で前提が違う
  • NIST SP 800-63B-4 第 5 章は、米国の政府向けの指針。SHALL と SHOULD を見分けて読む

Web 開発で点検することの全体は、Web 開発のセキュリティの基本にまとめています。

参考資料

この記事の内容は、2026年10月21日に開いた文書をもとにしています。OWASP の Cheat Sheet は頻繁に書き換わります。

※この記事は、公式の資料をもとに AI(Claude)で下書きし、2026年10月21日時点の公式の資料と照らして確かめてから公開しています。誤りに気づいたらお問い合わせからお知らせください。

PR
PR
タイトルとURLをコピーしました