3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Posted at

本記事で利用したパッケージ

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

3
4
0

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
  3. You can use dark theme
What you can do with signing up
3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?