Custom Laravel auth driver with Firebase

Aug 25, 2021 LaravelFirebase

Firebase Authentication is a super powerful and easy-to-use service to authenticate users to your app. It comes with a bunch of auth providers such as traditional email/password authentication, GitHub, Google, password-less mobile number authentication, and more.

It's pretty straightforward to authenticate a user in your frontend app, but you may need to authenticate the user on the backend api as well. When a user gets authenticated, Firebase will give the user a JWT token that contains the user info. By sending the JWT to the backend api, you can decode and verify the JWT and retrieve the user info on the backend. For more details, check out my blog post on how to verify Firebase JWT using Laravel.

In this post, I will show you how to customise Laravel auth driver using Firebase. I'm going to use the FirebaseToken class that I shared in my previous post.

Link Firebase users with users table

I assume that you have users table in your Laravel app for storing user data while using Firebase for authentication. We wouldn't store any additional attributes on Firebase user since it's not meant be used as a database.

This means that we need to link Firebase users with the users table records. We are going to store Firebase user id as the id column in the users table. Let's change the id column to string (i.e. varchar) type and drop unnecessary columns. Most of the default columns are not required if you use Firebase Authentication.

Schema::table('users', function (Blueprint $table) {
    $table->string('id')->change();
    $table->dropColumn('email_verified_at');
    $table->dropColumn('password');
    $table->dropColumn('remember_token');
});

Since we changed the primary key to a string column, we need to update the User eloequent model like below.

class User extends Authenticatable
{
    use HasFactory;
    use Notifiable;

    /**
     * The primary key associated with the table.
     *
     * @var string
     */
    protected $primaryKey = 'id';

    /**
     * Indicates if the IDs are auto-incrementing.
     *
     * @var bool
     */
    public $incrementing = false;

    /**
     * The "type" of the auto-incrementing ID.
     *
     * @var string
     */
    protected $keyType = 'string';
    
    ////
}

Create a new user

Now that you can store Firebase user id in the users table, let's create an endpoint to store a new user. Usually, you retrieve user info from the request body, however, we will retrieve it from JWT token that is passed via Authorization HTTP header.

public function store(Request $request)
{
    $token = new FirebaseToken($request->bearerToken());

    try {
        $payload = $token->verify(config('services.firebase.project_id'));
    } catch (\Exception $e) {
        // return error response
    }

    $user = User::create([
        'id' => $payload->user_id,
        'email' => $payload->email,
        'name' => $payload->name,
        'avatar' => $payload->avatar,
    ]);

    return (new UserResource($user))
        ->response()
        ->setStatusCode(Response::HTTP_CREATED);
}

Custom authentication driver

Next, create a custom authentication driver so that you can authenticate the user in subsequent requests.

Call the Auth::viaRequest method within the boot method of your AuthServiceProvider. The second argument passed to the method is a closure that receives the incoming HTTP request and returns a user instance or, if authentication fails, null.

public function boot()
{
    $this->registerPolicies();

    Auth::viaRequest('firebase', function (Request $request) {
        $token = $request->bearerToken();

        try {
            $payload = (new FirebaseToken($token))->verify(
                config('services.firebase.project_id')
            );

            return User::find($payload->user_id);
        } catch (\Exception $e) {
            return null;
        }
    });
}

Once you define custom auth driver, you can configure it as a driver within the guards configuration of your config/auth.php.

'guards' => [
    'api' => [
        'driver' => 'firebase',
    ],
],

Authenticate requests

Now, let's use the auth guard middleware and retreive the authenticated user data.

Route::middleware('auth:api')->group(function () {
    Route::apiResource('posts', PostController::class);
}
class PostController extends Controller
{
    public function index(Request $request)
    {
        // retrieve an authenticated user via request
        $request->user();
        
        ////
    }
}

Wrap up

You can use Firebase for authentication and store the user data in your Laravel application by decoding/verifying the JWT token. This way, you can leverage both Firebase's powerful authentication and Laravel's authentication guard. If you have any questions or comments, please comment in this Twitter thread!

avatar

About Me

I am a software engineer from Japan and currently based in Singapore. Over the past 5 years, I've been creating e-commerce and FinTech web applications using Laravel, Angular, and Vue.js. Making clean, maintainable, and scalable software with agile methodology is one of my biggest passions. Im my spare time, I play the bass and enjoy DIY.