【初心者向け】セッションを扱う実装をするときに知っておくべきこと

# はじめに
筆者は2022年8月に株式会社ニジボックスに入社し、これまで約1年数ヶ月ほどフロントエンドエンジニアとして実務経験を積んできました。複数のプロジェクトにて開発・テスト工程などコーディング中心のタスクをこなしてきたので、基本的な画面構築や簡単なロジックの実装はもちろん、ちゃんと要件がはっきり明記されているような実装タスクであればある程度自信をもって取り組めるようになったと思います。

しかし、どのプロジェクトでも一定数いるつよつよなエンジニアの方々は、セキュリティやパフォーマンス等の非機能要件の考慮も必要な、もっと複雑で難しい実装をしてますよね。（**ユーザーの認証・認可まわり**や**セッション管理**などなど）

なので今回は、そんな彼らに一歩でも近づくために、自分がまだ実務レベルで触ったことのない**セッション**について学んで整理してみることにしました。


## 対象読者
- フロントエンド初心者〜実務経験1〜2年目の人
- まだセッション周りの仕様理解や知識に自信が無い人

## 前提
- あくまで初心者向けの内容です
- セッション管理について深い理解があるわけではないため、詳しい人からすると内容が浅いと感じるかもしれません
- 要件やプロジェクト規模に応じたセッション管理のベストプラクティスなどについて高度な見解は書いてないです


# そもそもセッションとは
セッションとは、**Webサイトへのアクセス開始から終了までの一連の処理を表す概念・仕組み**のことです。
もっと具体的にわかりやすく説明すると、Webアプリ等でユーザーがログインしてからログアウトするまでの処理、ECサイトなどでカートに商品を入れてから購入するまでの処理などがセッションの代表例となります。

これらのセッションを適切に管理・活用することで、ログインの手間を減らすことができたり、ユーザーごとに最適なUIを提供することができます。

# なぜセッション管理が必要なのか
セッションが何なのかなんとなく理解できたら、次はなぜセッション管理をする必要があるのか深掘りしてみましょう。

結論から書くと、「**ステートレスなHTTPを使いつつも、ステートフルな通信を実現するため**」です。

## HTTPはステートレス
HTTPはWeb上の情報・データをクライアント（ブラウザ）とサーバーの間で送受信する時に使用される通信プロトコルで、クライアントからHTTP リクエストを送り、WebサーバーがクライアントにHTTP レスポンスを返すというシンプルな仕組みです。

そしてHTTPの重要な特性として、**ステートレス**が挙げられます。
ステートレスとは、**状態（state）がない（less）こと**を意味しており、ユーザーがログインしたり商品をカートに入れたりした時の状態を保持することができないということです。

一見すると不便に思われるかもしれませんが、この**ステートレス** かつ **クライアントからのデータ送信要求（HTTPリクエスト）とそれに対するWebサーバー側からの応答（HTTPレスポンス）の組み合わせで成り立つこのシンプルさ**が、Webの基幹プロトコルとして30年以上使われ続けていることに起因しているのかと思います。

## ステートフルな通信が必要な理由
HTTPがステートレスであることによって、先述したようにユーザーのログイン・ログアウト状態やECサイト等におけるカートの中身の状態などが**HTTP通信が行われる度に保持できず初期化されてしまいます**。

その結果、**ユーザーの識別やユーザーによる入力値の保持ができないので、UX（ユーザー体験）がひどく損なわれてしまう**わけですね。

**セッションを利用してステートフル（状態がある）な通信を実現する**ことによって、ユーザー側は一度ログインしたらサーバー側で設定された期限を過ぎるまでユーザー認証を求められることなくページの移動や操作が可能になりますし、システム側としても同じユーザーに対して何度もユーザー認証などの処理を行う必要がなくなります。

これらの理由から、実際多くのWebアプリケーションでセッション管理が行われているのです。

# セッションの具体的な仕組みと管理手法
では、セッションが具体的にどういう仕組みで管理されるのかもっと詳しくみていきましょう。
試しに Qiita にログインするときの動きを例に説明します。

１. ユーザーが Qiita の「ログイン」を押下する

![スクリーンショット 2023-12-16 14.46.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482472/a09663d3-57ed-a673-8590-0099f01c3631.png)

２. メールアドレス/パスワード もしくは Github や Google アカウントでログインする

![スクリーンショット 2023-12-16 14.46.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482472/9196b274-609b-ce56-1ebc-3e972713f100.png)


３. サーバーが**セッションID**と呼ばれるランダムなIDを生成してブラウザへ渡す（今回のケースだと Cookie に保存）

![スクリーンショット 2023-12-16 15.02.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482472/40a36729-8b9d-8c2f-87f9-30585b7d6a97.png)

![スクリーンショット 2023-12-16 15.12.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482472/81e0959b-967c-2c62-6367-757a1eb0aa26.png)


４. 次に同一ユーザーから Qiita へアクセスがあった場合は、**ブラウザの Cookie に保存してあるセッションIDのおかげでサーバーは同一ユーザーからのアクセスだと判別し、ログイン処理をスキップする**

![スクリーンショット 2023-12-16 15.45.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482472/45dc876c-afb2-4023-39bc-0799d81e0b9d.png)

![スクリーンショット 2023-12-16 16.06.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482472/6f00f0c6-f42b-15d3-1915-9417de7d8b84.png)

上記のセッション管理の例では、サーバーが発行したセッションIDをブラウザの Cookie に保存し、毎リクエスト時にそれをサーバーが読み取ってユーザーを特定することでログインの手間を省くことに成功しています。

ユーザーのログイン情報（ID、パスワード）やカートの中身などのセッション情報を丸ごと Cookie に保存するケース[^1]もありますが、**一般的にはセッションIDのみを Cookie に保存して、セッションIDと紐づくセッション情報は別のデータベースから取り出すパターンが多い**と思います。

[^1]: Railsなどではデフォルトでユーザー情報などを Cookie に保存するが、署名付きの暗号化が施されるためユーザーによって勝手に書き換えられないため、なりすましが発生する可能性は限りなく低い

セッション情報を管理する手法はいくつかあるので、それぞれ触れていきましょう。

## Cookie
先ほどの例にも出てきましたが、Webアプリケーションにおける**セッション管理の方法として最も一般的なのが Cookie** を用いたものです。

### 使い方

どのようにして Cookie に保存する値を設定するかは使用する開発言語やフレームワークによって異なりますが、結果的には以下のような `key=value` で指定した値を Cookie に保存することができます。（シンプルに動かして試したい場合は、[MDN - Document.cookie](https://developer.mozilla.org/ja/docs/Web/API/Document/cookie) に記載の通り `document.cookie="{key=value}"` で簡単に Cookie を保存できます）

```
{任意のセッションID名}={サーバーで生成したセッションID}; expires=Mon, 23 Dec 2024 00:00:00 GMT; domain=example.com; HttpOnly
```

[MDN - Set-Cookie > Syntax](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#syntax) を確認するとわかりますが、セッションID以外にも色々なオプションを指定することができ、Cookie の**有効期限**や**有効なドメイン**などが設定できます。ただ、**セッションIDやオプションの記載等含めて全部で4KBまでしか設定できない**ので注意してください。


## Local Storage / Session Storage
セッション管理の目的や用途によっては、Web ブラウザに内蔵されている **Web ストレージ**の利用も考えましょう。

Web ストレージは2種類あり、**Local Storage** と **Session Storage** があります。

|  | Local Storage | Session Storage |
|:-:|:-:|:-:|
| 保存領域 | ブラウザ | ブラウザ |
| 有効期限 | 消さない限りは半永久的に保持し続ける | セッションが切れたり、タブが閉じられるまで保持し続ける |
| 保存容量 | 5〜10MB | 5〜10MB |
| サーバーへのデータ送信 | なし | なし |
| 備考 | オリジン[^2]単位でデータを保存し、タブやウィンドウ、ブラウザが閉じられても永続し、次に参照されたときも、同じ内容を保持する | セッション単位でデータを保存し、セッションの開始とともに生成され、終了とともに消去される |

[^2]: スキーム（URLの「http://」の部分）、ドメイン（URLの「www.example.com」の部分）、ポート（URLの「:8080」の部分）を合わせたもの

**保存する容量が Cookie の上限である 4KB を超える場合**や、**サーバーと通信させずにブラウザ側だけでセッション管理を完結させたい場合**は、Web ストレージの利用がおすすめです。

Cookie を含め、これらストレージの使い分けとしては以下をイメージすればOKです。

- **Cookie** 
    - セッションID やユーザー情報（個人設定や閲覧履歴など）を期限付きで保存・管理
- **Local Storage**
    - タブやブラウザを閉じても消えてほしくない個人設定の保存やキャッシュの管理など
- **Session Storage**
    - カートの中身やフォームの入力状態など一時的な情報の保存など

### 使い方
[Local Storage](https://developer.mozilla.org/ja/docs/Web/API/Window/localStorage) と [Session Storage](https://developer.mozilla.org/ja/docs/Web/API/Window/sessionStorage) はどちらも Window オブジェクトから参照できるので、JavaScript でいきなり `localStorage.{登録や参照のメソッド}` と書くだけで使えます。データ構造は Cookie と同じく `key` と `value` をセットで指定したものです。

```js
// データの保存
localStorage.setItem("key", "value");
sessionStorage.setItem("key", "value");

// データの参照
const hoge = localStorage.getItem("key");
const fuga = sessionStorage.getItem("key");

// データの削除
localStorage.removeItem("key");
sessionStorage.removeItem("key");

// 全データの削除
localStorage.clear();
sessionStorage.clear();
```

両方ともシンプルで同一の書き方なので、覚えやすいですね。
ちなみに、Cookie と同様に開発者ツールで簡単に中身を参照することが可能です。

![スクリーンショット 2023-12-19 11.38.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482472/d1394230-c665-c2ac-de38-8c77bde7c3f8.png)


# セキュリティについて気をつけるべきこと
セッション情報を管理・応用することでブラウザ側がユーザーを識別できたりして便利な反面、もしそのユーザーの識別に使われるセッションIDが不正に利用されたりすると **なりすまし（セッションハイジャック）** や **機密情報の流出** などにつながる可能性があります。なので最後に、セッションを扱ううえで必ず知っておきたいセキュリティ対策や気をつけるべき観点について説明しておきます。

## Cookie のセキュアな利用
Cookie はセッション管理の方法として最も一般的に知られており、以下のような使い方でセッションID を Cookie で管理するパターンが多いです。

1. ユーザーがログイン時にそのユーザーを識別させるためのセッションIDをサーバー側で生成し、ブラウザの Cookie に保存する
2. ユーザーのログイン情報が入ったDBとは別に、セッションIDとそのユーザーを紐付けたID のみを別の格納場所（主にセッションストアと呼ばれる）に保存する
3. 次のログイン時には、ブラウザの Cookie に保存されたセッションIDをもとにセッションストア → DB の順に必要なユーザー情報を辿ってログイン状態を判別

なので、その Cookie をよりセキュアな方法で利用することはセッションIDの不正利用防止につながります。

### 有効期限
まず基本的な設定として、Cookie には適切な有効期限を設定しましょう。
あまりにも有効期限が長い場合、その分意図せず残ってしまっている Cookie が不正利用される可能性が高まってしまうからです。

ブラウザによって有効期限の上限値は異なるものの、Chrome の有効期の上限値[^3]である**400日以下で、可能な限り短い期間**を設定するのがよさそうです。

```
session_id=hogefugapiyo; expires={ここに適切な有効期限を入れる}; domain=example.com; HttpOnly
```

[^3]: これまで Chrome の Cookie の有効期限に制限はなく、1日でも、１か月でも、10年でも Cookie を発行する側の任意で決めることができました。 しかし、2022年8月にリリースされた Chrome 104 以降、Cookie の有効期限は400日を上限とする仕様に変更されました。

### Secure 属性と HttpOnly 属性
意図しない第三者や不正なスクリプトから Cookie が参照されないために、**Secure 属性**や **HttpOnly 属性**というものがあります。

- Secure 属性
    - **HTTPS通信のときだけサーバーに送信されます**
    - 通信が暗号化されていないHTTP通信を盗聴され、Cookie 情報を取得されてしまうことを防ぐことができます
- HttpOnly 属性
    - **JavaScriptからアクセスできなくなります**
    - これによりXSS攻撃などによるリスクを小さくすることができます

```
session_id=hogefugapiyo; expires=Mon, 23 Dec 2024 00:00:00 GMT; domain=example.com; ここ👉 HttpOnly 
```

## セッションID 自体をセキュアに
いくら Cookie をセキュアに利用したとしても、セッションIDまでアクセスを許してしまう可能性はあります。そんな時のために、セッションID自体にも工夫があるとよいですね。

### 推測されにくい文字列を使う
生成されるセッションIDに規則性があったり、極端に文字数が少なかったりすると、悪意のある攻撃者から簡単にセッションIDを特定されてしまいます。

セッションIDは、**推測されにくいように不規則**であるべきです。
便利なライブラリなどを活用して、`7C1QjS3R9oiDyRHLW4GI9laPCBYEDvw2Vs0MuQYmdZw` のようなランダム生成した文字列を使うようにしましょう。

文字数の目安ですが、OWASP[^4] が公開している [Insufficient Session-ID Length](https://owasp.org/www-community/vulnerabilities/Insufficient_Session-ID_Length) によると、**最低でも128ビット以上の長さ（文字数でいうと32文字以上）** を推奨しており、それ以下の文字数のセッションIDは ブルートフォース攻撃（総当たり攻撃）で特定される危険性が十分にあるとのことです。

[^4]: Open Web Application Security Project の略で、webアプリケーションのセキュリティに関する研究や
脆弱性診断ツールの開発などを行うアメリカ合衆国発の非営利団体

### セッションIDを認証成功する度に再生成
**セッションフィクセーション（セッション固定化）攻撃**と呼ばれる、**攻撃者が用意したセッションIDをターゲットのユーザーに強制的に使わせることでそのセッションを乗っ取る手法**も存在します。

そういった攻撃への対策として、ユーザーがログイン時など**認証成功したタイミングで都度新しいセッションIDを生成する**ことで、古いセッションIDを攻撃者に利用させないようにすることができます。

セッションの再生成はフレームワークやライブラリで容易に組み込むことができる場合も多いので、使用している言語・フレームワークのライブラリ等をぜひ検索してみてください。


# おわりに
今回はセッションを扱うときに知っておくべきことを初心者なりにまとめてみました。
できれば Auth.js（旧: NextAuth.js）の話とか SPA における JWT を用いたセッション管理 のような、もっとモダンフロントエンド開発に即した内容を書きたかったのですが、私自身の理解の範疇を超えていたのでいったん断念しました。もっと時間をかけて自信と実力がついてからリトライしようと思います。

誤字や脱字のご指摘はもちろん、見当違いなことを書いている箇所があればコメントください。

最後まで読んでいただき、ありがとうございました。


# 参考文献

https://qiita.com/shimotaroo/items/e4a1011d040ffc7f56f6

https://zenn.dev/tm35/articles/584ece2d771a4b

https://qiita.com/niihara_megumu/items/be693500d42088027547

https://www.sunapro.com/session-hijack/#index_id2

https://gihyo.jp/dev/serial/01/front-end_web/000301

https://zenn.dev/oreilly_ota/articles/2e5c5d3212107d

https://zenn.dev/powder/articles/session_rails_20221124