search
LoginSignup
2

posted at

'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ファイルにある設定を組み込みます。

.env
GOOGLE_CLOUD_PROJECT=XXXXXXXXX

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

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

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の設定場所を定義します。

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トークンを取得するところから

$idTokenString = $request->headers->get('authorization');

Keyは、authorizationになっており、Valueが、JWTトークンです。

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

上記は、verifyIdToken()メソッドを使用してIDトークンを確認します。
トークンは、1時間で有効期限が切れます。

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

トークン確認が終わり、認証済みのトークンに切り替わり?ます。
(トークンをデコードしただけらしい?)
このトークンからuid, emailなどユーザー情報を取得することができます。
上記は、ユーザー情報を取得してきています。

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

return $next($request);

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

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

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が実装されている感じになります。

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などよろしくお願いします!

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
2