Laravel Passportを実際に体験してみる

# 概要

- Laravel Passportを導入してちょっと使ってみた時のメモ

# 導入方法

1. 下記コマンドを実行してライブラリそのものをインストールする。

    ```terminal
    $ composer require laravel/passport
    ```

1. 下記コマンドを実行してマイグレーションを行う。

    ```terminal
    $ php artisan migrate
    ```

1. 下記コマンドを実行してPassportをインストールする。（トークンを作成するための暗号化キーとPersonal access clientとPassword grant clientが作成されるらしい。）

    ```terminal
    $ php artisan passport:install
    ```

1. 上記コマンドで発行された各種情報はDBのoauth_clientsテーブルに自動で格納される。
1. `use Laravel\Passport\HasApiTokens;`の記載を`App/Models/User.php`に記載する。

    ```App/Models/User.php
    use Laravel\Passport\HasApiTokens;

    class User extends Authenticatable
    {
        use HasApiTokens;
        use HasFactory;
        use Notifiable;
    }
    ```

1. `app/Providers/AuthServiceProvider.php`に下記の様に記載する。

    ```app/Providers/AuthServiceProvider.php
    use Laravel\Passport\Passport;

    // 省略

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        if (! $this->app->routesAreCached()) {
            Passport::routes();
        }
    }
    ```

1. `config/auth.php`のguardsのapiのdriverをpassportに変更する。（apiの記載がない場合は記載する。）

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

1. `config`直下を修正したので下記コマンドを実行してconfigのキャッシュクリアを行う。

    ```terminal
    $ php artisan config:clear
    ```

1. 下記コマンドを実行してUsersテーブルにデータを格納するシーダーを作成する。

    ```terminal
    $ php artisan make:seed UserSeeder
    ```

1. UserSeederファイルを下記の様に記載する。

    ```databases/seeders/UserSeeder.php
    <?php
    
    namespace Database\Seeders;
    
    use Illuminate\Database\Seeder;
    use Illuminate\Support\Facades\DB;
    use Illuminate\Support\Facades\Hash;
    
    class UserSeeder extends Seeder
    {
        /**
         * Run the database seeds.
         *
         * @return void
         */
        public function run()
        {
            DB::table('users')->insert([
                'id' => 1,
                'name' => 'test_user',
                'email' => 'test@example.com',
                'password' => Hash::make('p@ssw0rd'),
            ]);
        }
    }
    ```

1. DatabasesSeederファイルを下記の様に記載する。

    ```databases/seeders/DatabaseSeeder.php
    <?php
    
    namespace Database\Seeders;
    
    use Illuminate\Database\Seeder;
    use Database\Seeders\UserSeeder;
    
    class DatabaseSeeder extends Seeder
    {
        /**
         * Seed the application's database.
         *
         * @return void
         */
        public function run()
        {
            $this->call([
                UserSeeder::class,
            ]);
        }
    }

    ```

1. 下記コマンドを実行してseederを流す。

    ```terminal
    $ php artisan db:seed
    ```

1. アクセストークン取得後に実行テストするための処理を準備する。`routes/api.php`を開き、下記の内容を記載する。

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

# 使ってみる(Password Grant）

1. 本方法はOAuth2では推奨されていない。しかしながら簡単でわかりやすいので、まずはPassword Grantというものを使ってトークン取得してみようと思う。
    - 推奨されているGrant Type → [https://oauth2.thephpleague.com/authorization-server/which-grant/](https://oauth2.thephpleague.com/authorization-server/which-grant/)
1. リクエストを送ることのできるツール（Postmanやthunder client）を用いて下記のリクエストを送る。
    - リクエストの詳細

    | 項目 | 値 | 備考 |
    | --- | --- | --- |
    | リクエスト送信URL | /oauth/token | 環境に応じて<br>`http://localhost:ポート番号`や<br>`http://127.0.0.1/oauth/token`として指定する。 |
    | HTTPメソッド | POST |  |

    - POSTする値の情報（BodyのFormとして下記の値を送信する。）

    | 項目 | 値 | 備考 |
    | --- | --- | --- |
    | grant_type | password | Password Grantを使う指定 |
    | client_id | 2 | `$ php artisan passport:install`を実行した時に<br>oauth_clientsテーブルに格納された<br>nameカラムの値が「Laravel Password Grant Client」のレコードの<br>idカラムの値 |
    | client_secret | mPzZA8moXEe7iPzflQQWWjjsL6Lh3gC2hhArACyP | `$ php artisan passport:install`を実行した時に<br>oauth_clientsテーブルに格納された<br>nameカラムの値が「Laravel Password Grant Client」のレコードの<br>secretカラムの値<br>※左に記載されている値は皆さんのものとは異なります |
    | username | text@example.com | seederで登録したユーザーのメールアドレス |
    | password | p@ssword | seederで登録したユーザーのパスワード |
    | scope |  | 何も入力しない |

1. 筆者は下記の様な情報でリクエストを送った。

    ![get-token-password.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/fd9e1f86-da1c-6b4d-cc2a-47b2f125bb74.png)

1. 下記のような値が帰ってきた。認証が通り、アクセストークンとリフレッシュトークンが返された。（今回はテスト用に起動したアプリケーションで実施しているため戻り値を公開しているが、本来はあまり見せないほうが良い。）

    ```
    {
      "token_type": "Bearer",
      "expires_in": 31536000,
      "access_token": "eyJ0eXAiOiJKV1QiLCJhbGci・・・",
      "refresh_token": "def50200a6be2104b700f49020130a・・・"
    }
    ```

    ![get-token.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/a860e48e-32bc-c194-655a-009ad411196c.png)

1. 帰ってきた値の`access_token`を用いてリクエストを送ってみる。リクエストを送ることのできるツール（Postmanやthunder client）を用いて下記のリクエストを送る。

    - リクエストの詳細

    | 項目 | 値 | 備考 |
    | --- | --- | --- |
    | リクエスト送信URL | /api/user | 環境に応じて<br>`http://localhost:ポート番号`や<br>`http://127.0.0.1/oauth/token`として指定する。 |
    | HTTPメソッド | GET |  |

    - POSTする値の情報（AuthのOAuth 2に下記の項目を追加して送信する。）

    | 項目 | 値 | 備考 |
    | --- | --- | --- |
    | Access Token | eyJ0eXAiOiJKV1QiLCJhbGci・・・ | 先のリクエストで戻ってきたaccess_tokenの値をすべてコピーして記載する。 |

1. 筆者は`/api/user`のリクエストのAuthタブのAccess Token部分に先のリクエストでの戻り値の`access_token`を記載しリクエストを実行した。

    ![New_Request.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/aab0c3db-b901-561b-0e38-143a6f8366a2.png)

1. 下記の様な値が帰ってきた。`routes/api.php`に記載されている`/user`のルーティングに紐づくクロージャーの処理が実行されユーザー情報が返された。

    ```
    {
      "id": 1,
      "name": "test_user",
      "email": "test@example.com",
      "email_verified_at": null,
      "created_at": null,
      "updated_at": null
    }
    ```

    ![get-user.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/f2cfa6a5-b54f-28a9-085b-9af1d04fae53.png)

1. 上記の様に一回access_tokenが吐き出されたら、有効期限が切れるまでaccess_tokenを用いて通信を行う。access_tokenの有効期限が切れたら、refresh_tokenを用いてaccess_tokenとrefresh_tokenを再発行する。refresh_tokenの有効期限も切れてしまったら、ユーザーに再度ログインをお願いする。

# 使ってみる(Client Credentials Grant）

1. 次にユーザー情報を必要としないClient Credetials Grantを使ってみる。ユーザー情報を必要としないのでシステムそのものからのAPI呼び出し時などにトークンを利用して認証する事ができる。
1. 下記コマンドを実行してClient Credentials Grantのクライアントを作成する。（新しいクライアント情報がoauth_clientsテーブルに追加される。）

    ```terminal
    $ php artisan passport:client --client
    What should we name the client? [Laravel ClientCredentials Grant Client]:
    >クライアントの任意の名前を指定する事ができる。今回はデフォルトのままEnterを押下する。 
    ```

1. クライアントのクレデンシャルチェック用のミドルウエアを追加して`routes/api.php`で指定してアクセス制限をかけられるようにする。`app/Http/Kernel.php`を開く。`$routeMiddleware`の配列の最後に下記の内容を追記する。（これで`routes/api.php`で`->middleware(api:client)`のように`routes/api.php`で指定できるようになった。）

    ```app/Http/Kernel.php
    'client' => \Laravel\Passport\Http\Middleware\CheckClientCredentials::class,
    ```

1. リクエストを送ることのできるツール（Postmanやthunder client）を用いて下記のリクエストを送る。
    - リクエストの詳細

    | 項目 | 値 | 備考 |
    | --- | --- | --- |
    | リクエスト送信URL | /oauth/token | 環境に応じて<br>`http://localhost:ポート番号`や<br>`http://127.0.0.1/oauth/token`として指定する。 |
    | HTTPメソッド | POST |  |

    - POSTする値の情報（BodyのFormとして下記の値を送信する。）

    | 項目 | 値 | 備考 |
    | --- | --- | --- |
    | grant_type | client_credentials | Client Credentials Grant Clientを使う指定 |
    | client_id | 3 | `$ php artisan passport:client --client`を実行した時に<br>oauth_clientsテーブルに格納された<br>nameカラムの値が「Laravel ClientCredentials Grant Client」のレコードの<br>idカラムの値 |
    | client_secret | YDu9jKONVeS5eOgGwCEtuefcRT8rsIXEocHmVQhO | `$ php artisan passport:client --client`を実行した時に<br>oauth_clientsテーブルに格納された<br>nameカラムの値が「Laravel ClientCredentials Grant Client」のレコードの<br>secretカラムの値<br>※左に記載されている値は皆さんのものとは異なります |
    | scope |  | 何も入力しない |

1. 筆者は下記のようなリクエストを送った。

    ![get-token-org-client.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/d0abfec5-43fe-22b9-4ced-bfe9860a21ba.png)


1. 下記のような値が帰ってきた。Password Grantの時と同じように認証が通り、アクセストークンとリフレッシュトークンが返された。

    ```
    {
      "token_type": "Bearer",
      "expires_in": 31536000,
      "access_token": 
      "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiO・・・
    }
    ```

    ![get-token-org-client.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/84a27abd-b192-1698-8d6e-57d710408e15.png)

1. あとはPassword Grantのときと同じ様に帰ってきた値の`access_token`を用いてリクエストを送ってみる。リクエストを送ることのできるツール（Postmanやthunder client）を用いて下記のリクエストを送る。

    - リクエストの詳細

    | 項目 | 値 | 備考 |
    | --- | --- | --- |
    | リクエスト送信URL | /api/user | 環境に応じて<br>`http://localhost:ポート番号`や<br>`http://127.0.0.1/oauth/token`として指定する。 |
    | HTTPメソッド | GET |  |

    - POSTする値の情報（AuthのOAuth 2に下記の項目を追加して送信する。）

    | 項目 | 値 | 備考 |
    | --- | --- | --- |
    | Access Token | eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiO・・・ | 先のリクエストで戻ってきたaccess_tokenの値をすべてコピーして記載する。 |

1. 筆者は下記のような情報でリクエストを送った。

![get-user.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/dd1a64e7-82d5-aaeb-fc69-c50e4cfa19b5.png)

1. Password Grantのときと同じように下記の様な値が帰ってきた。`routes/api.php`に記載されている`/user`のルーティングに紐づくクロージャーの処理が実行されユーザー情報が返された。

    ```
    {
      "id": 1,
      "name": "test_user",
      "email": "test@example.com",
      "email_verified_at": null,
      "created_at": null,
      "updated_at": null
    }
    ```

    ![get-user.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/c8a31220-d8d9-8aaa-b67e-06b99770e88d.png)

# 参考文献

- [Laravel 9.x Laravel Passport](https://readouble.com/laravel/9.x/ja/passport.html)
- [Laravel Passport](https://laravel.com/docs/9.x/passport)
- [https://reffect.co.jp/laravel/laravel-passport-understand](https://reffect.co.jp/laravel/laravel-passport-understand)
- [Laravel 9.x スターターキット](https://readouble.com/laravel/9.x/ja/starter-kits.html)