[Laravel]異なるサイト間でCookieとSessionを共有してログイン状態を保持する

## 概要
異なるサイト間で**Cookie**と**Session**を共有してログイン状態を保持する手順を記載します。
片方のサイトでログインすればもう片方もログイン状態になり、片方でログアウトすればもう片方もログアウトするような感じです。
Laravelで以下の条件を満たしていれば、基本的に**設定ファイルを編集するだけ**でできます。

- サイト間で認証方式が同じ
- サイト間でドメインが同じ
- サイト間で同じデータベースを使用している

今回は、Laravelでデフォルトの認証方式である`$ php artisan make:auth`を両方のプロジェクトで実行したとして話を進めます。
独自の認証だったり他のライブラリ等を利用した場合は、設定に関する変更箇所を適宜見つける必要があります。

## Cookie/Sessionとは
ステートレス(状態を持たない)である**HTTP通信**において、ログイン状態などの状態を保持しておきたいときに**Cookie**と**Session**を用います。
Cookieはクライアント側で持つ情報、Sessionはサーバー側で持つ情報です。
これらの情報は連想配列のように取得することが可能です。

参考：[【PHP超入門】Cookieとセッションについて](https://qiita.com/7968/items/ce03feb17c8eaa6e4672)

## 環境
Laravel 5.5.45
Homestead 8.2.0
Vagrant 2.2.4
VirtualBox 7.2.1

## 手順
### Cookieをサブドメイン間で共有する
#### 1.認証ドライバの設定
**ガードにsessionを指定**します。
`$ php artisan make:auth`のコマンドで作成した認証方法であれば、あらかじめ設定されているため変更不要ですが、内容を確認してみます。
**ガード(Guard)**とは、Authの認証に利用しているドライバークラスで、各リクエストごとにどのようにユーザーを認証するかを定義します。

各プロジェクトで`config/auth.php`を開き、**web**のガードが認証ドライバとして**session**を指定していることを確認します。

```config/auth.php
    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
```

また、デフォルトで**web**のガードが使用されていることを確認します。

```config/auth.php
    'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],
```

#### 2.APP_URLの設定
`.env`ファイルのAPP_URLを設定します。
各プロジェクトで、正しいURLを設定してください。

```.env
- APP_URL=http://localhost
+ APP_URL=http://shared-session.example.test
```

#### 2.ドメインの設定
Cookieの**ドメインの設定**をします。
ドメインを指定した場合、Cookieはそのドメイン自体だけでなく、サブドメインに対しても送信されます。

Cookieの仕様で、.comや.jpといった**トップレベルドメイン**に対してはdomain属性は設定しても**無効**になります。
また、ブラウザによっては.co.jpなど**一般的なドメインも無効**になる場合があります。

各プロジェクトの`.env`ファイルに**SESSION_DOMAIN**を追加し、サイトで使用しているドメインを指定します。

```.env
SESSION_DRIVER=file
SESSION_LIFETIME=120
+ SESSION_DOMAIN=.example.test
```

この定数は`config/session.php` で読み込まれています。
`.env`ファイルにSESSION_DOMAINが存在しない場合にnullを返しています。

```config/session.php
    'domain' => env('SESSION_DOMAIN', null),
```

#### 3.Cookie名の設定
Cookie名を指定します。
各プロジェクトの`.env`ファイルに**SESSION_COOKIE**を追加し、任意のCookie名を指定します。

```.env
SESSION_DRIVER=file
SESSION_LIFETIME=120
+ SESSION_COOKIE=shared_cookie
```

もしくは、各プロジェクトの`.env`ファイルの**APP_NAME**を同じにします。
`config/session.php`で、SESSION_COOKIEがない場合にAPP_NAMEを使用するよう指定しているためです。

```.env
APP_NAME=Laravel
```

```config/session.php
    'cookie' => env(
        'SESSION_COOKIE',
        str_slug(env('APP_NAME', 'laravel'), '_').'_session'
    ),
```

#### 4.Cookieを復号するためのキーの設定
各プロジェクトの`.env`ファイルで**APP_KEY**を同じにします。
Cookieに含まれてるセッションIDは暗号化されているため、同一キーでないと復号できないためです。
片方のをコピーして、もう片方のプロジェクトへ貼り付けます。

```.env
+ APP_KEY=base64:+UeQnKa......=
```

#### 5.確認
Cookieがサブドメイン間で共有されていることを確認していきます。
サーバーを再起動し、両方のサイトでログインします。

ブラウザがChromeの場合、 **右上のメニューアイコン > 設定 > 詳細設定 > コンテンツの設定 > Cookie > すべての Cookie とサイトデータを表示** を開きます。
参考：[Chromeでcookieを確認する方法: 小粋空間](http://www.koikikukan.com/archives/2017/12/13-000300.php)

検索バーで「example.test」を検索します。
Cookieがサブドメインごとではなく、指定したドメインに対して作成されています。
![スクリーンショット 2019-04-28 15.05.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140953/d8740d41-02e1-1357-960f-728be310365f.png)

クリックすると、SESSION_COOKIEで指定したCookie名になっています。
![スクリーンショット 2019-04-28 15.07.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140953/9d0ded34-9249-cc6b-8193-2c613d2146f2.png)

#### 参考
- [Laravel5系でサブドメイン間のCookie共有でハマった話 | ブログ :: Web notes.log](https://blog.wnotes.net/blog/article/laravel-cookie-subdomain-sharing)
- [Sharing Cookies with Subdomains in Laravel](https://willbrowning.me/sharing-cookies-with-subdomains-in-laravel/)

## Sessionをデータベースで共有する

#### 1.Sessionの保存先を設定
Sessionの保存先をデータベースに変更します。
各プロジェクトの`.env`ファイルで**SESSION_DRIVER**をfileからdatabaseに変更します。

```.env
- SESSION_DRIVER=file
+ SESSION_DRIVER=database
```

この定数は`config/session.php` で読み込まれています。

```config/session.php
  'driver' => env('SESSION_DRIVER', 'file'),
```
#### 2.Sessionのテーブルを作成
以下のコマンドでSession用の**マイグレーションファイル**を作成し、実行してSessionテーブルを作成します。
この操作はどちらか片方のプロジェクトでOKです。

```
$ php artisan session:table
$ php artisan migrate
```

#### 3.確認
これでSessionの共有は完了です。
片方でサイトでログインしてみて、もう片方のサイトでリロードするとログイン状態になれば成功です。
![スクリーンショット 2019-04-28 17.48.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140953/0bcc958d-ca91-2230-d116-282271f5b1ab.png)


また、データベースでSessionテーブルへアクセスしてみてセッションのデータが作成されているか確認してみてください。

```bash
$ mysql -u root -p
Enter password:
mysql> show databases;
mysql> use <table名>;
mysql> show tables;
mysql> select * from sessions;
```

#### 参考

- [Saba note | Laravel5 | Laravel5でデータベースセッションを使う](https://saba.omnioo.com/note/2457/)

## 補足

#### うまく行かない場合
以下の方法等を試してください。

- 設定を片方のプロジェクトのみしていないか確認する
- `storage/framework/sessions`フォルダ内の不要なセッションファイルを消してみる
- ブラウザのCookie情報を消してみる
(右上のメニューアイコン > 設定 > 詳細設定 >閲覧履歴データを削除する > Cookieとキャッシュの項目をチェックして「データを削除」をクリック)
- サーバーを再起動してみる 
(Homesteadを使用してる場合は `$ vagrant reload —provision` を実行)

#### セキュリティについて
CookieとSessionを共有することに関してのセキュリティについて考えます。
Cookieに関しては、domainを指定することによってCookieが送信される範囲が広がるため、挙動を理解しておく必要があります。
[CookieのDomain属性は *指定しない* が一番安全 | 徳丸浩の日記](https://blog.tokumaru.org/2011/10/cookiedomain.html)
[Cookie - サブドメイン間のCookieの共有｜teratail](https://teratail.com/questions/92184)

また、CookieとSessionを共有することによって片方の情報が漏洩すること=もう片方も漏洩することになるので、漏洩した場合に影響範囲が広くなります。

#### 複数のデータベースを使用している場合
以下が参考になります。

- [Laravel5.5で同一接続先ホストでの複数DBトランザクションを行う方法 - Qiita](https://qiita.com/advy-kobayashi/items/c0a9fe212523467f8d7f#_reference-8c094e39f0d3d798d96e) 

#### ドメインが全く異なる場合
**シングルサインオン(SSO)**を使用すると実現できると思います。

## 最後に
記事に間違いや不明な点があれば遠慮なくご指摘ください。