'Firebase for Laravel'を使ったログイン実装(middleware編)

## 本記事で利用したパッケージ
* Firebase for Laravel

https://github.com/kreait/laravel-firebase#support
上記の設定などは、各自README通りにお願い致します。
主に、`Authentication`を利用しています。
→Authenticationの利用方法(https://firebase-php.readthedocs.io/en/stable/authentication.html)
*自分は、composerを利用しました。


## 本記事の目標
* FirebaseのAuthenticationの情報をLaravelで利用できる
* Firebase x Laravel でログイン機能の実装ができる。
* FirebaseのJWTトークンをフロントのHeaderから受け取り、middleware上でデコード、認証しFirebaseのuidとemailを取得できる。
* 取得したuidとemailが、他のController上で利用できるようにする。(ログイン認証したユーザーだけが動かせるロジックを組み込みため)
* Input整理のためのOutput

## 実装
まず、.envファイルにある設定を組み込みます。

```php:.env
GOOGLE_CLOUD_PROJECT=XXXXXXXXX
```
https://firebase.google.com/docs/admin/setup#initialize_the_sdk

上記の記事に記載されている通り、GOOGLE_CLOUD_PROJECTに利用しているFirebase Authenticationのプロジェクト名を記載する。
→これにより、Googleの公開鍵を取得し、認証情報を取得します。

今回は、middlewareで実装し、Controller内で取得した値を利用できるように実装します。
middlewareの設定をします。

```php:Kernel.php
protected $routeMiddleware = [
    'auth' => \App\Http\Middleware\Authenticate::class,
    'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
    'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
    'can' => \Illuminate\Auth\Middleware\Authorize::class,
    'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
    'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
    'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
    'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
    'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
    'firebase' => \App\Http\Middleware\Firebase::class,
];
```
`'firebase' => \App\Http\Middleware\Firebase::class,`
でmiddlewareの設定場所を定義します。

```php:Firebase.php
<?php

namespace App\Http\Middleware;

use Kreait\Firebase\Contract\Auth;
use Kreait\Firebase\Exception\Auth\FailedToVerifyToken;

class Firebase
{
    private Auth $auth;
    public function __construct(Auth $auth)
    {
        $this->auth = $auth;
    }

    public function handle($request, \Closure $next)
    {
        $idTokenString = $request->headers->get('authorization');
        $token = trim(str_replace('Bearer', '', $idTokenString));

        try {
            $verifiedIdToken = $this->auth->verifyIdToken($token);
        } catch (FailedToVerifyToken $e) {
            echo 'The token is invalid: '.$e->getMessage();
        }

        $firebaseId = $verifiedIdToken->claims()->get('sub');
        $email = $verifiedIdToken->claims()->get('email');

        $request->merge([
            'firebaseId' => $firebaseId,
            'email' => $email,
        ]);

        return $next($request);
    }
}
```
`Kreait\Firebase\Contract\Auth`こちらの使い方は、パッケージのドキュメントで確認してください。
今回は、フロントから送られてくるHeader内にJWTトークンを含ませています。
まずは、JWTトークンを取得するところから
```php:
$idTokenString = $request->headers->get('authorization');
```
Keyは、`authorization`になっており、Valueが、JWTトークンです。

```php
try {
    $verifiedIdToken = $this->auth->verifyIdToken($token);
} catch (FailedToVerifyToken $e) {
    echo 'The token is invalid: '.$e->getMessage();
}
```
上記は、`verifyIdToken()`メソッドを使用してIDトークンを確認します。
トークンは、１時間で有効期限が切れます。

```php
$firebaseId = $verifiedIdToken->claims()->get('sub');
$email = $verifiedIdToken->claims()->get('email');
```
トークン確認が終わり、認証済みのトークンに切り替わり？ます。
(トークンをデコードしただけらしい？)
このトークンから`uid`, `email`などユーザー情報を取得することができます。
上記は、ユーザー情報を取得してきています。

```php
$request->merge([
    'firebaseId' => $firebaseId,
    'email' => $email,
]);

return $next($request);
```
あとは、取得した値をControllerに渡す際に、requestに混ぜてしまいます。
(セキュリティ的になんともいえなさそうな雰囲気を感じます。)→コメントでご教授願います。

では、ルーティングの設定を行います。
middlewareの処理をControllerに入る前に挟むかどうかを決めてます。
```php:api.php
<?php

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

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

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

Route::middleware(['firebase'])->group(function (){
    Route::post('/user', UserController::class);
});

```
group内のみmiddlewareの`firebase`が適用されています。
UseControllerの実装前にmiddlewareのfirebase.phpが実装されている感じになります。

```php:UserController
<?php

namespace App\Http\Controllers;

use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class UserController extends Controller
{
    public function __construct()
    {
        $this->middleware('firebase');
    }

    public function __invoke(Request $request): JsonResponse
    {
        $firebaseId = $request->firebaseId;
        $email = $request->email;
        var_dump($firebaseId);
        var_dump($email);

        $response = [
            'firebase_id' => $firebaseId,
            'email' => $email,
        ];

        var_dump($response);

        return response()->json($response, Response::HTTP_CREATED);
    }
}
```

$requestの中に値を入れているので、上記のようなにすれば値を取得できます。
`var_dump`で値の中身を見てみましょう！
当たり前かと思いますが、responseに含めることも可能です！

つまり、ルーティングのgroupに入れてしまえば、認証済みユーザーの情報をController内で使用できます。


## まとめ
middlewareでController前にログインの認証(やっていることはほぼデコードであるが、)を行えるようにしたい！ってなったけど、他に記事的な物がなかったのでこれは記事にできそう！と思い、書きました。
初学者的には、middlewareに実装の想像もできず、少し苦戦していましたが、徐々に理解して勉強になりました。

一応、クリーンアーキテクチャで実装している時に行ったので、クリーンアーキテクチャでも流用できると思います。(依存の方向が正しいか不安...)
別の方法で、UseCase層で実装している方法も行いました。時間があれば、そちらも記事にしたいと思います。

徐々にOutput用に記事、記載していけたらと思います。
コメントやLGTMなどよろしくお願いします！