Laravelで画面上でログインしているときだけ叩けるAPIを実装し、Postmanによる動作確認を行う

# 概要

LaravelのAPIを実装し、次のようなことを調査・実装し、Postmanのあまりの便利さに感動したのでまとめる。
これがベスト、ではなくこう実装した、という記録。

- ログイン中のユーザだけが利用できるようにする方法。
  - 実装したAPIのうち、ユーザ情報を返すAPIがあるため。未ログインのユーザには見せたくない。
  - APIは、モーダル上に情報を表示したり、取得した情報をセレクトボックスの選択肢として表示するのに使われる。
- APIの動作確認のため、PostmanでCookie付きでリクエストする方法。
  - ログインしていれば200レスポンスが返ること、ログアウト状態なら情報が取得できないことを確かめたい。
- (おまけ)実際にjsでajax通信する際は、どのように認証させればいいのか。



# 動作確認環境

```
Laravel 6.20.2
PHP 7.4
```



# ログイン中のユーザだけが利用できるように、セッションIDで認証する

今回の開発においてはAPIトークンやLaravel Passportを使った実装を行う予定がないため、Laravelのデフォルトで用意されている`api`ミドルウェアグループの定義を書き換える。

## ミドルウェアの追加

Laravelのデフォルトのままでは `route/api.php`に記述されたルーティングにはCookieとセッションによる認証に関するミドルウェアが設定されていない。
APIに対してCookieとセッションによる認証を有効化するため、`Kernel.php`にミドルウェアを4点追加する。



`app/Http/Kernel.php`

```php
    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
        ★★★ここから★★★
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        ★★★ここまで★★★
            'throttle:60,1',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];
```



## APIのルーティングを`auth`ミドルウェアを利用するように修正する


`route/api.php`には、デフォルトだと次のようにただログイン中ユーザの情報を返すAPIが記述されている。
ここでは`auth:api`というように、api guardが利用されている。

```php
Route::middleware('auth:api')->get('/user', function (Request $request) {
    return $request->user();
});
```



これを、`auth`に書き換える。

```php
Route::middleware('auth')->group(function () {
    // ルーティングも実装したAPIに書き換える
    Route::get('/users', xxxxxx);
});
```



### 他、試したこと

当初、`config/auth.php`のapi guardの部分を、

```php
    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],

        'api' => [
            'driver' => 'token',
            'provider' => 'users',
            'hash' => false,
        ],
    ],
```

下記のように、 auth.phpで定義したguardを`route/api.php`でミドルウェアに設定すればいいと思ったが、そうしても認証できなかった。

(SessionDriverが生成されること、Illuminate\Auth\GuardHelper の
authenticate()で$this->user()が空になっていることまでは突き止めた。なのでキャッシュが残っていることが原因ではない。
src/Illuminate/Auth/SessionGuard.phpのuser()のなかでユーザが取得できていないっぽい。でも、driverとproviderを同じものにしているのにミドルウェアを`auth:api`→`auth`にすると認証できる理由がわからない・・・。)

```php
        'api' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
```

結局、`config/auth.php`は書き換えず、ミドルウェアを`auth:api`から`auth`に書き換えることにした。

何か分かれば追記する。



## 参考

- [Laravelで「画面ログインしてるときだけ叩けるAPI」を実装した際の備忘録](http://koba5884.com/archives/11907413.html)
- [LaravelでセッションIDで認証状態をチェックするステートフルなAPIを使う](https://qiita.com/pinekta/items/d10c8374b1a3003cd952)
- [CookieによるAPI経由のユーザー認証機能を作る【Laravel6とNuxt.jsで作る管理画面】](https://deha.co.jp/magazine/admin-laravel-nuxt-cookie-auth)
- [Laravelのmiddleware authを理解したい（6.x）](https://qiita.com/darum/items/82f5b0ab83f175c45687)



# PostmanでCookie付きでリクエストする

ログインしているユーザのみ利用できるように、Cookieとセッションを使った認証を行うようにしたため、PostmanでAPIの動作確認をするには、Cookie付きでリクエストする必要がある。

以下の手順で設定を行う。

- Google拡張機能 [Postman Interceptor](https://chrome.google.com/webstore/detail/postman-interceptor/aicmkgpgakddgnaphhhpliifpcfhicfo?hl=ja) をインストールする。

- 「Capture Cookies」をONにし、動作確認をしたい環境のドメインを追加する。
  localの場合は `localhost` で、ポート番号は不要。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253650/8e2effeb-141f-9bd5-f531-532f4c97fccb.png)

- Postman側でも、Interceptor Bridgeをインストールする。
  (`Capture requests and cookies with Postman`のアイコンから。初回のみ)

- 「Capture Cookies」をONにし、動作確認をしたい環境のドメインを追加する。
  localの場合は `localhost` で、ポート番号は不要。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253650/3c137bbc-ad7a-3828-b7eb-d1702be1a4f1.png)

- 以上の手順でクッキーがキャプチャーされている状態になる。
ブラウザでlocalhostにアクセスし、Postman側でCookies(下記画像参照)にクッキーが含まれていることを確認する。 (手動で書き換えることも可能と思われるが、勝手にcaptureしてくれるので使う場面がなかった。)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253650/19c8be22-7b4c-4c29-ce1e-9fe4d251da7e.png)

画面上でログインした状態でAPIを正常なリクエストすると200レスポンスが返り、ログアウト状態でリクエストすると 401 Unauthorize が返ることを確認できればOK。
ログインしているはずなのに401が返ってくる、というときは、Cookieをcaptureできているかどうか確認すること。



## 参考

- [「Postman Intercepter」Cookie利用時のREST用ツール](https://qiita.com/katsu_suzuki/items/fb8036776b4513366995)

  - 4年前の記事なのでインターフェースなどが異なるものの、
    Postman側もInterceptorをONにする必要があるなど、使い方がざっくり分かったので助かりました。

    

## その他注意事項
ブラウザ側のInterceptorとPostmanがDISCONNECT状態になることがあったが、ブラウザまたはPostmanを再起動すれば正常に接続できた。

## おまけ Postmanでパスパラメータを指定したい場合
[今更わかるPostman〜便利なAPI開発補助ツール〜](https://rarejob-tech-dept.hatenablog.com/entry/2019/12/13/190005)より
>pathパラメータはURL中に:param_nameで設定

することで、 `Query Params` の下に `Path Variables` が出現します。なんだこの隠しコマンドみたいな技・・・。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/253650/33d2f8bb-478a-cba3-b51a-6b17c77baa2f.png)

# (参考)ajax通信する際、同一ドメインに対してはCookieを送信するようになっている

実装したAPIを画面側で呼び出す際は、Cookie情報は一切触れていない。
なぜなら、呼び出す側と呼び出すAPIが同一ドメインである場合、Cookieは勝手に送信されるようになっているから。

ajaxで呼び出す際の実際のコードはこんな感じ。
ログインさえしていれば、呼び出すことができる。

```js
    $.ajax({
        url: '/api/xxxxx/',
        type: 'GET',
        contentType: 'application/json; charset=UTF-8',
        data: {
            // 「type: 'GET'」と指定しているので、自動でGETパラメータとして扱われる
        }
    }).then((res) => {
        // モーダルに表示したり、セレクトボックスの選択肢として追加したり、etc...
    });
```



## 参考

- [jqueryの.ajax（）メソッドがセッションCookieを送信しないのはなぜですか？](https://www.it-swarm-ja.tech/ja/jquery/jqueryの.ajax（）メソッドがセッションCookieを送信しないのはなぜですか？/969621771/)

  - このサイトを参考にしたが、あまり信憑性がないので参考まで。

  - 『Webを支える技術』『安全なWebアプリケーションの作り方』も確認したが、『Webを支える技術』には明確な記述はなかった。
    『安全なWebアプリケーションの作り方』p.65 に、

    > いったんクッキー値を覚えたブラウザは、
    > その後同じサイト(example.jp)にリクエストを送信する際には、覚えたクッキー値(PHPSESSID=・・・)を送信します。

    と記載があった。
javascriptでリクエストしようがPHPでリクエストしようが対象が同一ドメインなら、ブラウザはCookieを送信するようになっている、と捉えることにした。