****************
* 2020/08/13 追記
* コメントでCSRFについてのご指摘をいただきました。
* セキュリティに関わる事ですので、注意して自己判断で参考にして頂ければと思います。
****************
Webサイトによくある導線として、「入力画面→確認画面→完了画面」というのがあると思います。
- 入力画面で入力された内容をセッションに入れて、
- 入力内容を確認画面で表示して、
- 完了画面でセッションの情報を登録する
という処理が想像できます。
ここでたまに起きる問題として、別タブで同じ画面を開き、タブ同士を行ったり来たりしながら完了画面に行くと確認画面と違う内容が登録されてしまうというもの。
なぜ?
セッションの仕組みはcookieにセッションIDを保存して成り立っているので、通常「1ブラウザ = 1cookie = 1セッション」となります。
だから、ブラウザのタブを複数開いていた場合でも、セッションはタブ毎ではなく全タブで共通となる。
つまり、同じキーでセッションに値を格納したら後勝ち(上書き保存)になるのです。
例
手順 | タブ | 操作内容 | 備考 | セッションの中身 |
---|---|---|---|---|
1 | タブ1 | 入力画面を開く | - | - |
2 | タブ1 | 入力して確認画面に遷移する | ここでセッションに入力内容を格納する | 入力内容1 |
3 | タブ2 | 新しいタブで入力画面を開く | - | 入力内容1 |
4 | タブ2 | タブ1の時とは違う内容を入力して確認画面に遷移する | ここでセッションに入力内容を格納する | 入力内容2 |
5 | タブ1 | タブ1に戻って完了画面に遷移する | セッションにある入力内容2の情報を登録する | 入力内容2 |
このような手順で操作をした場合、ユーザー的には「タブ1」の確認画面で入力した内容(入力情報1)が表示されているので、当然それが登録されると思うが、実際は「手順4」でセッション内容が「入力内容2」に上書きされているので、最終的には「入力内容2」が登録されている。
対策
そこで、セッションの中身をちょっと工夫してみます。
セッションに格納する時のキーをタブ毎に用意して、その中でさらにkey:valueにしてデータを格納します。
イメージはこんな感じ。
[
タブ1のキー => [
'name' => 'やまだたろう',
'email' =>'yamada@example.com',
],
タブ2のキー => [
'name' => 'たなかいちろう',
'email' =>'tanaka@example.com',
],
]
ポイント
- タブ毎のキーは、ミリ秒単位のタイムスタンプとかUUIDとか絶対に被らないIDを生成する
- タブ毎に生成したキーを画面に隠し持たせる。
<input type="hidden" name="tab_key" value="【生成したタブのキー】">
- ビジネスロジックでセッションを使うときはリクエストの生成したタブのキーを使ってデータを取得する。(セッションに値を追加する時も)
さらに
- セキュリティー的には、生成したタブのキーは画面遷移毎とか一定時間毎に生成しなおすと良いかも
- セッションタイムアウト機能を持たせておいて、一定時間過ぎたタブのセッションは削除する様にするとcookieを圧迫しないで済むかも