Laravel10 + ReactでSPA作成 [Part 1]

以下のサイトを日本語にしてみました。（ページ全てを訳してはいません）
誤りがあったらご指摘いただけると嬉しいです。

https://dcodemania.com/post/full-stack-laravel-10-react-js-spa-authentication-part-1

## Step 1: Laravelをインストールする
ここでは「backend」という名でプロジェクトを作成しています。
```sh:command
composer create-project laravel/laravel backend
```

## Step 2: .envファイルの設定
> `SESSION_DOMAIN`と`SANCTUM_STATEFUL_DOMAINS`を追加することがセキュリティのために推奨されます。
>
>`SESSION_DOMAIN`はセッションクッキーのドメインを設定し、セッションハイジャックやCSRF攻撃などのクッキーベースの攻撃を防ぐのに役立ちます。
ドメインを指定しない場合、Laravelはアプリケーションの現在のドメインを使用します。
>
>`SANCTUM_STATEFUL_DOMAINS`は、Laravel Sanctumで保護されたアプリケーションのエンドポイントへの[ステートフル](https://www.cloud-for-all.com/azure/blog/difference-stateful-and-stateless)なリクエストを許可するドメインのリストを指定します。
これは、クロスサイトリクエストフォージェリ（CSRF）攻撃を防ぐのに役立ちます。
>
>`SESSION_DOMAIN`と`SANCTUM_STATEFUL_DOMAINS`を設定しない場合、Laravelはデフォルトでアプリケーションの現在のドメインを使用します。
しかし、アプリケーションが複数のドメインやサブドメインからアクセスされる場合は、セキュリティを強化するために、これらの変数を設定することをお勧めします。
>
>データベースの認証情報を入力し、`SESSION_DRIVER=cookie`を設定します。
>`SESSION_DRIVER=cookie`は、Laravelがセッションデータを保存するためにクッキーを使用していることを意味します。
ユーザーがLaravelアプリケーションにログインすると、セッションデータを保存するためにブラウザにクッキーが設定されます。
このクッキーは、その後のリクエストごとにサーバーに送り返され、Laravelはユーザーのセッションデータを取得し、リクエストにまたがって状態を維持することができます。
>
>セッションの保存にクッキーを使うことは、ウェブ・アプリケーションでは一般的な慣行です。
しかし、クッキーはクロスサイトスクリプティング（XSS）やクロスサイトリクエストフォージェリ（CSRF）のような特定のタイプの攻撃に対して脆弱である可能性があることに注意することが重要です。
Laravelには、これらのタイプの攻撃に対するビルトインの保護が含まれていますが、それでもリスクを認識し、適切なセキュリティ対策を講じることが重要です。

つまり、セキュリティに気をつけるために`SESSION_DOMAIN`と`SANCTUM_STATEFUL_DOMAINS`を追加しましょうということです。

```.env
FRONTEND_URL=http://localhost:3000
SESSION_DOMAIN=localhost
SANCTUM_STATEFUL_DOMAINS=localhost:3000

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=full_stack_laravel_react_js
DB_USERNAME=root
DB_PASSWORD=

BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=cookie
SESSION_LIFETIME=120
```

## Step 3: Laravel Sanctumのファイルを更新する
### app/Http/Kernal.php
`app/Http/Kernal.php`ファイルを開き、`$middlewareGroups`と`api`セクションの下にあるステートフルリクエスト用の`\LaravelSanctum\HttpMiddleware\EnsureFrontendRequestsAreStateful::class`をコメント解除してください。
```php:app/Http/Kernal.php
protected $middlewareGroups = [
  'web' => [
      \App\Http\Middleware\EncryptCookies::class,
      \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
      \Illuminate\Session\Middleware\StartSession::class,
      \Illuminate\View\Middleware\ShareErrorsFromSession::class,
      \App\Http\Middleware\VerifyCsrfToken::class,
      \Illuminate\Routing\Middleware\SubstituteBindings::class,
  ],

  'api' => [
      \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
      \Illuminate\Routing\Middleware\ThrottleRequests::class . ':api',
      \Illuminate\Routing\Middleware\SubstituteBindings::class,
  ],
];
```

`\EnsureFrontendRequestsAreStateful::class`は、Laravel Sanctumが提供するミドルウェア・クラスです。
このミドルウェアは、アプリケーションのフロントエンドからのリクエストがステートフルなリクエストとして扱われ、認証を必要とする保護されたルートにアクセスできるようにするために使用します。

このミドルウェアは、暗号化されたCSRFトークンを含むクッキーをレスポンスに追加し、フロントエンドが後続のリクエストごとにこのトークンを送り返すことを期待します。
このようにして、ミドルウェアはCSRFトークンをチェックし、リクエストしたユーザーを特定し、保護されたルートにアクセスできるようにします。

こちらはこちらのサイトの方がわかりやすいと思います。

https://www.maasaablog.com/blog/kei1dibfn0r

>EnsureFrontendRequestsAreStatefulクラスは、ざっくりと以下のような処理を行う
>
>・セッションクッキーのセキュア設定を強制
・cookie のスコープ(参照・操作の権限)を HTTP リクエストに制限し、javascriptなどから直接参照・操作されないようにする
・クッキーの暗号化
・レスポンスにクッキー付与(\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class)
・セッションの開始
・laravel_sessionをレスポンスに追加
・CSRFトークンを検証する

### config/cors.php
次に、`config/cors.php`を開き、以下の部分を`false`から`true`に変更します。
```php:config/cors.php
'supports_credentials' => true
```

上記はLaravel Sanctumの設定オプションで、クロスサイトリクエストにクッキーやその他のクレデンシャル（証明書）を含めることができます。

異なるドメインやサブドメインにまたがってリクエストを行う場合、ブラウザはセキュリティ上の理由からリクエストをブロックすることがあります。
これはセイム・オリジン・ポリシーとして知られており、あるドメインのウェブページが別のドメインのリソースとどのように相互作用できるかを制限します。

クロスサイトリクエストを許可するには、CORS（Cross-Origin Resource Sharing）ヘッダーを使用します。
Laravel Sanctumは、EnsureFrontendRequestsAreStatefulというミドルウェアを提供し、Sanctumがクッキーで正しく動作するために必要なCORSヘッダーを設定します。

`supports_credentials' => true` 設定オプションは、Cookie やその他の認証情報をクロスサイトリクエストに含めるべきであることを示すために使用されます。
このオプションが `false` に設定されている場合、CORS ヘッダはクレデンシャルを含まず、ブラウザはリクエストと共にクッキーやその他の認証情報を送信しません。

今回はフロントとAPIのドメインが異なるため、corsの設定が必要です。
この値をtrueにしてあげないと、異なるドメインからのリクエスト時にエラーになってしまいます。

## Step 4: テーブルのmigrate
Laravel Sanctumを使用するために、追加のマイグレーションを作成する必要はありません。
マイグレーションを実行するだけなので、マイグレーションを実行するには、以下のコマンドを使用するだけです：

```sh:command
php artisan migrate
```

## Step 5: Controllerの作成
以下のコマンドで新しいControllerを作成してください。
```sh:command
php artisan make:controller AuthController
```
`make:controller`コマンドは、新しいコントローラクラスを生成するためのショートカットです。
`php artisan make:controller AuthController`を実行すると、Laravelは`app/Http/Controllers`ディレクトリに`AuthController.php`という新しいファイルを作成します。
このファイルには、認証ロジックの出発点として使用できるいくつかの基本的なメソッドを持つスケルトンコントローラクラスが含まれます。

## Step 6: Rotesの作成
AuthController クラスを作成したら、 それを使用してアプリケーションの `routes/api.php` ファイルでルートを定義します。たとえば、以下のコードのように AuthController の login メソッドに対応するログインルートを定義します

`routes/api.php`を開き、以下のように記述してください。
```php:routes/api.php
<?php

use App\Http\Controllers\AuthController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "api" middleware group. Make something great!
|
*/

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

Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login']);

Route::middleware('auth:sanctum')->group(function() {
    Route::post('/logout', [AuthController::class, 'logout']);
    Route::get('/user', [AuthController::class, 'user']);
});
```

`auth:sanctum`ミドルウェアはLaravel Sanctumによって提供され、Sanctumによって生成されたAPIトークンを使用してリクエストを認証するために使用されます。
`auth:sanctum`ミドルウェアを使用するルートにリクエストが行われると、Sanctumはリクエストに有効なAPIトークンが含まれているかどうかをチェックします。
トークンが有効な場合、リクエストの続行が許可されます。
トークンが無効または見つからない場合、リクエストは`401 Unauthorized`レスポンスで拒否されます。

## Step 7: Login & RegisterのRequests作成
以下のコマンドで新しいrequestを作成してください。
```sh:command
php artisan make:request RegisterRequest
```
`php artisan make:request RegisterRequest`は、アプリケーションの`app/Http/Requests`ディレクトリに`RegisterRequest`という新しいフォームリクエストクラスを生成するLaravelのコマンドです。

Laravelのフォームリクエストは、入力されたHTTPリクエストを検証するために使用されます。
リクエストと共に送信されたデータを検証するルールを定義し、アプリケーションが処理する前に、データが特定の要件を満たしていることを確認するために使用できます。

`app/Http/Requests/RegisterRequest.php`を開き、以下のように記述してください。
```php:app/Http/Requests/RegisterRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Password;

class RegisterRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
     */
    public function rules(): array
    {
        return [
            'name' => 'required|string|max:255',
            'email' => 'required|email|unique:users,email|max:255',
            'password' => [
                'required',
                'string',
                Password::min(8)->mixedCase()->numbers()->symbols()->uncompromised(),
                'confirmed',
            ]
        ];
    }
}
```

同様に、以下のコマンドで新しいrequestを作成してください。
```sh:commnad
php artisan make:request LoginRequest
```

`app/Http/Requests/LoginRequest.php`を開き、以下のように記述してください。
```php:app/Http/Requests/LoginRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class LoginRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
     */
    public function rules(): array
    {
        return [
            'email' => 'required|email|exists:users,email',
            'password' => 'required|string',
        ];
    }
}

```

## Step 8: UserモデルのResourceの作成
以下のコマンドで新しいresourceを作成してください。
```sh:command
php artisan make:resource UserResource
```

Laravelのリソースクラスは、アプリケーションのモデルからJSONレスポンスで返せる形式にデータを変換するために使用されます。
これらは、レスポンスで返されるデータをカスタマイズする方法を提供し、機密情報を隠したり、関連するデータを含めたり、その他のデータ変換を実行するために使用できます。

`make:resource`コマンドは、新しいリソースクラスを生成するためのショートカットです。
`php artisan make:resource UserResource`を実行すると、Laravelは`app/Http/ResourcesディレクトリにUserResource.php`という新しいファイルを作成します。
このファイルには、JSONレスポンスで返すデータを定義するために使用できる`toArray`メソッドを持つスケルトンリソースクラスが含まれます。

`app/Http/Resources/UserResource.php`を開き、以下のように記述してください。
```php:app/Http/Resources/UserResource.php
<?php

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class UserResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'created_at' => $this->created_at,
            'updated_at' => $this->updated_at,
        ];
    }
}
```

## Step 9: AuthControllerのすべてのメソッドを定義する
すべてのファイルが準備できたので、次は `AuthController.php` ファイルですべてのメソッドを定義します。
以下に、ユーザー登録、ユーザーログイン、ユーザー情報の取得、ユーザーログアウトのメソッドを定義します。

```php:app/Http/Controllers/AuthController.php
<?php

namespace App\Http\Controllers;

use App\Http\Requests\LoginRequest;
use App\Http\Requests\RegisterRequest;
use App\Http\Resources\UserResource;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;

class AuthController extends Controller
{
    // ユーザー登録
    public function register(RegisterRequest $request) {
        $data = $request->validated();

        $user = User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
        ]);

        $token = $user->createToken('auth_token')->plainTextToken;

        $cookie = cookie('token', $token, 60 * 24); // 1日

        return response()->json([
            'user' => new UserResource($user),
        ])->withCookie($cookie);
    }

    // ログイン
    public function login(LoginRequest $request) {
        $data = $request->validated();

        $user = User::where('email', $data['email'])->first();

        if (!$user || !Hash::check($data['password'], $user->password)) {
            return response()->json([
                'message' => 'メールアドレスかパスワードが正しくありません'
            ], 401);
        }

        $token = $user->createToken('auth_token')->plainTextToken;

        $cookie = cookie('token', $token, 60 * 24);

        return response()->json([
            'user' => new UserResource($user),
        ])->withCookie($cookie);
    }

    // ログアウト
    public function logout(Request $request) {
        $request->user()->currentAccessToken()->delete();

        $cookie = cookie()->forget('token');

        return response()->json([
            'message' => 'ログアウトしました'
        ])->withCookie($cookie);
    }

    // ユーザー情報取得
    public function user(Request $request) {
        return new UserResource($request->user());
    }
}
```

### ユーザー登録メソッドの説明
このコードはLaravelアプリケーションの`AuthController`クラスの`register`メソッドで、新規ユーザーの登録を処理します。

このメソッドは最初に、リクエストのバリデーションルールを含む `RegisterRequest` クラスを使用してリクエストデータをバリデートします。

バリデート後、このメソッドはバリデートされたデータで新しいユーザーを作成し、Laravelの`Hash`ファサードを使用してユーザーのパスワードを安全に暗号化して保存します。
ユーザーオブジェクトの`createToken`メソッドは、今後のAPIリクエストの認証に使用される、ユーザーの新しいパーソナルアクセストークンを作成します。

トークンは、HTTPクッキーの作成に使用され、トークンおよびユーザー・データとともに、JSONオブジェクトとしてレスポンスに返されます。
`UserResource` クラスは、ユーザーデータをレスポンスに適した形式に変換するために使用されます。

クッキーの有効期限は1日、または`60 * 24`分に設定されています。

この方法により、ユーザーは各リクエストにアクセストークンを手動で含めることなく、複数のリクエストにわたって認証ステータスを維持することができます。
その代わりに、アクセストークンはHTTPクッキーに保存され、その後のリクエストごとに自動的に送信されます。

### ログインメソッドの説明
このコードはLaravelアプリケーションの`AuthController`クラスのログインメソッドで、ユーザー認証の処理を担当します。

このメソッドはまず、リクエストの検証ルールを含む`LoginRequest`クラスを使用してリクエストデータを検証します。

バリデーションの後、このメソッドは `User` モデルの `where` メソッドを使用して、指定されたメールアドレスを持つユーザを見つけようとします。
ユーザが見つからない場合、または提供されたパスワードがユーザのハッシュ化されたパスワードと一致しない場合、電子メールまたはパスワードが間違っていることを示すメッセージとともに`401 Unauthorized`レスポンスが返されます。

ユーザーの認証に成功すると、このメソッドは、ユーザー・オブジェクトの `createToken` メソッドを使用して、そのユーザーの新しい個人アクセストークンを作成します。
このトークンを使用してHTTPクッキーが作成され、トークンとユーザ・データとともにJSONオブジェクトとしてレスポンスに返されます。
`UserResource` クラスは、ユーザーデータをレスポンスに適した形式に変換するために使用されます。

クッキーの有効期限は1日、または`60 * 24`分に設定されています。

この方法により、ユーザーは各リクエストにアクセストークンを手動で含めることなく、複数のリクエストにわたって認証ステータスを維持することができます。
その代わりに、アクセストークンはHTTPクッキーに保存され、その後のリクエストごとに自動的に送信されます。

### ユーザー情報取得メソッドの説明
このコードはLaravelアプリケーションの`AuthController`クラスの`user`メソッドで、認証されたユーザーのデータを返す役割を担っています。

このメソッドは、有効なユーザーセッションとそのセッションに関連付けられたアクセストークンを含む`Request`オブジェクトが渡されることを期待します。
リクエストオブジェクトの `user` メソッドは、認証されたユーザーをセッションから取得するために使われます。

このメソッドは、認証されたユーザ・オブジェクトをコンストラクタに渡して、`UserResource`クラスの新しいインスタンスを作成します。
`UserResource`クラスは、ユーザデータをレスポンスに適した形式に変換するために使用され、通常、機密データや不要なデータを削除し、残りのデータをよりユーザフレンドリな方法でフォーマットします。

最後に、このメソッドは変換されたユーザーデータをJSONオブジェクトとして返します。
このアプローチは、ユーザのデータが認証されたユーザにのみ返され、クライアントに返される前に機密データがフィルタリングされることを保証します。

## Step 10: リクエストごとにクッキートークンを設定する

ここが重要なステップです。
`app/Http/Middleware/Authenticate.php`ファイルを編集します。
以下のコードを参照してください。

```php:app/Http/Middleware/Authenticate.php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
use Illuminate\Http\Request;

class Authenticate extends Middleware
{
    /**
     * Get the path the user should be redirected to when they are not authenticated.
     */
    protected function redirectTo(Request $request): ?string
    {
        return $request->expectsJson() ? null : route('login');
    }

    public function handle($request, Closure $next, ...$guards) {

        if ($token = $request->cookie('token')) {
            $request->headers->set('Authorization', 'Bearer ' . $token);
        }

        $this->authenticate($request, $guards);

        return $next($request);
    }
}
```
このコードは、Laravelアプリケーションの`Authenticate`ミドルウェアの`handle`メソッドで、ユーザーリクエストの認証を担当します。

このメソッドには、`$request` オブジェクトと `$next` クロージャ、そしてオプションの `$guards` パラメータを渡します。
`$guards` パラメータは、リクエストの認証に使用する認証ガードを指定するために使用します。

このメソッドは、まずリクエストクッキーにユーザのアクセストークンが含まれているかどうかを調べます。
トークンが見つかった場合は、それをリクエストヘッダに追加します。

このメソッドは次に `authenticate` メソッドを呼び出し、リクエストの実際の認証を実行します。
`authenticate` メソッドは、ユーザが認証され、アクセストークンが有効であることを確認します。

認証に成功すると、メソッドは認証済みの `$request` オブジェクトを使用して `$next` クロージャをコールします。
これにより、リクエストはスタックの次のミドルウェアに進むことができます。

このアプローチは、リクエストクッキーに存在する場合、ユーザーのアクセストークンがリクエストに自動的に添付され、ユーザーに代わって機密性の高いアクションが実行される前に、ユーザーが適切に認証されることを保証します。


## 次に向けて
認証されたユーザの詳細を取得し、ログアウトするために、フロントエンドから明示的にベアラートークンを送信する必要はありません。
セキュアな方法を使用しているため、httpのみのクッキーをクライアントのブラウザのクッキーに保存し、セッションの詳細もサーバに保存しています。
そのため、リクエストが送信されるたびに、クッキーはクライアント側からサーバーに自動的に送信され、残りはサーバー側で処理されます。
次のパートでは、reactアプリを作成してこのAPIを利用する方法を紹介します。