laravel-doctrineで認証

# はじめに
前回の記事の続きです。今回はlaravel-doctrineを使いつつ、Authファサードで認証を行うところまで行きます。

https://qiita.com/ggg-mzkr/items/4ef7710272c540fa8c0a


# laravel-doctrineで認証

## Userエンティティの更新

まず、Laravelで認証できるようにUserエンティティを更新します。

あるクラスをLaravelで認証できるようにするためには、そのクラスに`Illuminate\Contracts\Auth\Authenticatable`を実装する必要があります。また、そのクラスをデータベースから取得してくる`Illuminate\Contracts\Auth\UserProvider`クラスを作る必要があります。

詳しくはこちらのドキュメントをご覧ください。

https://readouble.com/laravel/8.x/ja/authentication.html#the-authenticatable-contract


https://readouble.com/laravel/8.x/ja/authentication.html#the-user-provider-contract

laravel-doctrineでは`Illuminate\Contracts\Auth\Authenticatable`の実装に必要なメソッドを既に定義した`trait`と、そのプロバイダーが用意されているのでこれらを使います。

また、ユーザーを一意に識別するカラムとして`email`を追加しています。

```php:app/Entities/User.php
<?php

namespace App\Entities;

use Doctrine\ORM\Mapping as ORM;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Support\Arrayable;
use LaravelDoctrine\ORM\Auth\Authenticatable as AuthenticatableTrait;

/**
 * @ORM\Entity
 */
class User implements Arrayable, Authenticatable
{
    use AuthenticatableTrait;

    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @ORM\Column(type="string")
     */
    protected $name;

    /**
     * @ORM\Column(type="string")
     */
    protected $email;

    public function getName()
    {
        return $this->name;
    }

    public function setName($name): self
    {
        $this->name = $name;
        return $this;
    }

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email): self
    {
        $this->email = $email;
        return $this;
    }

    public function toArray()
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
        ];
    }
}

```

エンティティを更新したら、マイグレーションの作成と適用をします。

```
sail artisan doctrine:migrations:diff
sail artisan doctrine:migrations:migrate
```


## configの更新

laravel-doctrineが提供しているUseProviderを使うように設定ファイルを更新します。

```php:config/auth.php
...
    'providers' => [
        'users' => [
            'driver' => 'doctrine', // laravel-doctrineが提供しているUserProvider
            'model' => \App\Entities\User::class, // 変更
        ],
...
```


## コントローラーの作成とルートの登録

コントローラーを作成し、`login()`、`register()`、`profile()`アクションを作成します。
また、今回は認証にCookieを使うので、`api`ミドルウェアにCookieを使う設定を入れます。

```php:app/Http/kernel.php
    protected $middlewareGroups = [
...
...
        'api' => [
            'throttle:api',
            \Illuminate\Session\Middleware\StartSession::class, // 追加
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];
...
...
```

```php:routes/api.php
<?php

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

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

```

```php:app/Http/Request/LoginRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Auth\Events\Lockout;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

class LoginRequest extends FormRequest
{
    public function authorize()
    {
        return true;
    }
    
    public function rules()
    {
        return [
            'email' => 'required|string|email',
            'password' => 'required|string',
        ];
    }
}

```

```php:app/Http/Request/RegisterRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Auth\Events\Lockout;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

class RegisterRequest extends FormRequest
{
    public function authorize()
    {
        return true;
    }
    
    public function rules()
    {
        return [
            'name' => 'required|string',
            'email' => 'required|string|email',
            'password' => 'required|string',
        ];
    }
}

```

`login()`ではポストパラメータで受け取った`email`と`password`を用いて、認証を行います。
`Illuminate\Contracts\Auth\Authenticatable`、`Illuminate\Contracts\Auth\UserProvider`を使用していますので、LaravelのAuthファサードを使って認証を行うことができています。

`register()`ではポストパラメータで受け取ったデータを使ってUserをDBに登録します。前の記事でやったのと同じようにDBへの永続化は`EntityManager`を使って行います。パスワードは平文ではなく、`Illuminate\Contracts\Hashing\Hasher`を使って暗号化してから保存する必要があります。

`profile()`では認証済みのUserを取得して、データをレスポンスで返します。こちらも`login()`と同様にAuthファサードを使うことができます。

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

namespace App\Http\Controllers;

use App\Entities\User;
use App\Http\Requests\LoginRequest;
use App\Http\Requests\RegisterRequest;
use Doctrine\ORM\EntityManagerInterface;
use Illuminate\Contracts\Hashing\Hasher;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Auth;

class AuthController extends Controller
{
    public function login(LoginRequest $request): JsonResponse
    {
        if (!Auth::attempt($request->validated())) {
            return response()->json([
                'Invalid credential'
            ], 400);
        }

        return response()->json([]);
    }

    public function register(RegisterRequest $request, Hasher $hasher, EntityManagerInterface $entityManager): JsonResponse
    {
        $input = $request->validated();

        $user = new User();
        $user->setName($input['name']);
        $user->setEmail($input['email']);
        $user->setPassword($hasher->make($input['password']));

        $entityManager->persist($user);
        $entityManager->flush();

        return response()->json($user);
    }

    public function profile(): JsonResponse
    {
        $user = Auth::user();
        return response()->json($user);
    }
}

```


curlで実際に叩くとこのような感じです。


```bash
# Userを登録
$ curl http://localhost/api/register -d "name=TARO&email=test@example.com&password=password"
{"id":1,"name":"TARO"}


# 登録したUserでログイン
$ curl -vvv http://localhost/api/login -d "email=test@example.com&password=password"
....
....
< Set-Cookie: laravel_session=vjJ4KExxhGZt0qjtlxcw6w4tL3lsGalm8687V8O3; expires=Thu, 17-Jun-2021 16:20:09 GMT; Max-Age=7200; path=/; httponly; samesite=lax
...
...
[]%

# Cookieなしで叩くとレスポンスデータは空ですが
$ curl http://localhost/api/profile
{}

# Cookieをつけると認証が通ります
$ curl http://localhost/api/profile -b laravel_session=vjJ4KExxhGZt0qjtlxcw6w4tL3lsGalm8687V8O3
{"id":1,"name":"TARO"}
```

# 終わりに

laravel-doctrineを使った認証ですが、想像していたよりだいぶ楽でした。`UserProvider`と`Authenticatable`の実装が提供されているのが大きかったです。laravelでは色々な`interface`が定義されており、これらを実装するだけで手軽に連携できるのがいいですね
。改めて`interface`の重要さを感じました。