Secure your laravel API with Google Firebase Auth
Adding the Middleware to your API will ensure that access is granted only using a valid Bearer Token issued by Goggle Firebase Auth.
The main difference between this package and the package we forked it from is that we are using laravel-firebase as a dependency which in turn depends on firebase-php. Using that package instead of firebase-tokens (which is already included in firebase-php) removes the need for a service provider in this package since it is already included in laravel-firebase. Since that package depends on firebase-php, you also can use all the features that package provides.
This package includes optional role middleware for more granular access.
composer require sdwru/laravel-firebase-auth-plus
Publish the laravel-firebase ServiceProvider (Provider: Kreait\Laravel\Firebase\ServiceProvider
) if not already done so.
php artisan vendor:publish
Configure laravel-firebase according to their instructions and also explained in the official firebase documentation at this link.
Those instructions make it sound more complicated than it is. All we need to do is generate a JSON file as follows:
- In the Firebase console, open Settings > Service Accounts.
- Click Generate New Private Key, then confirm by clicking Generate Key.
- Securely store the generated JSON file and add a reference to that file in your laravel
.env
file. The following example assumes we are storing the file in the root folder of our laravel installation. Rename it to whatever you want.
FIREBASE_CREDENTIALS=myproject-firebase-adminsdk.json
There are two ways to use this.
Add the Middleware on your app/Http/Kernel.php file.
\sdwru\LaravelFirebaseAuth\Middleware\JWTAuth::class,
Refer to the Laravel Middleware documentation on where you can put this in your Kernel.php file and how it can be used in routes.
Add the Guard to app/Providers/AuthServiceProvider.php
in the boot
method.
public function boot()
{
$this->registerPolicies();
$this->app['auth']->viaRequest('firebase', function ($request) {
return app(\sdwru\LaravelFirebaseAuth\Guard::class)->user($request);
});
}
In config/auth.php
set your api guard driver to firebase
and the model to LaravelFirebaseAuth\User::class
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'firebase',
'provider' => 'firebase',
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
'firebase' => [
'driver' => 'firebase',
'model' => \sdwru\LaravelFirebaseAuth\User::class,
],
],
Add authentication to api routes in routes/api.php
.
Route::middleware('auth:api')->get('/user', function (Request $request) {
return $request->user();
//return true;
});
Route::middleware('auth:api')->apiResource('some_endpoint', 'API\SomeEndpointController');
<?php
namespace App\Http\Controllers\API;
use Illuminate\Http\Request;
use Illuminate\Contracts\Auth\Guard;
class UserController extends Controller
{
public function foo(Request $request, Guard $guard)
{
// Retrieve Firebase uid from id token via request
$user = $request->user();
$uid = $user->getAuthIdentifier();
// Or, do the same thing using guard instead
$user = $guard->user();
$uid = $user->getAuthIdentifier();
// Do something with the request for this user
}
}
Example: Check if logged in and retrieve firebase user object and uid (For method #2 only) from almost anywhere else inside Laravel
use Illuminate\Support\Facades\Auth;
class SomeClass
{
public function bar()
{
//Check if logged in and retrieve user object and uid using Auth Facade
$isLoggedIn = Auth::guard('api')->check();
$userObject = Auth::guard('api')->user();
$uid = Auth::guard('api')->id();
//Alternatively, use auth() helper
$isLoggedIn = auth('api')->check();
$userObject = auth('api')->user();
$uid = auth('api')->id();
}
}
To use this optional feature add the following to app/Http/Kernel.php
.
protected $routeMiddleware = [
...
...
'role' => \sdwru\LaravelFirebaseAuth\Middleware\Role::class,
];
Please note, the client needs to be issued a new token for the new role to take effect. This can happen in one of 3 ways according to the documentation. The user signs in or re-authenticates, the user session gets it's ID token refreshed after an older token expires, and ID token is force refreshed by calling currentUser.getIdToken(true)
on the client end in Javascript/Vue etc.
<?php
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use \Kreait\Firebase\Auth;
class UserController extends Controller
{
public $auth;
public function __construct(Auth $auth)
{
$this->auth = $auth;
}
public function index(Request $request)
{
$users = $this->auth->listUsers($defaultMaxResults = 1000, $defaultBatchSize = 1000);
foreach ($users as $k => $v) {
$response[$k] = $v;
}
echo json_encode($response);
}
public function update(Request $request, $uid)
{
$this->validate($request, [
'role' => 'present|string|max:20',
]);
$customAttributes = [
'role' => $request->role,
];
$updatedUser = $this->auth->setCustomUserAttributes($uid, $customAttributes);
return $this->auth->getUser($uid);
}
}
After assigning roles, add them to routes/api.php
.
// Allow any authenticated user
Route::middleware('auth:api')->apiResource('users', 'API\UserController');
// Only allow users with admin and foo roles
Route::middleware('auth:api', 'role:admin, foo')->apiResource('users', 'API\FooController');
// Allow users with admin role only
Route::middleware('auth:api', 'role:admin')->apiResource('users', 'API\AdminController');
The firebase-php sdk refers to the property where we assign roles as custom "attributes". Firebase and JWT refers to them as custom "claims". The important thing to understand is they are referring to the same thing.
https://firebase.google.com/docs/auth/admin/custom-claims
https://firebase.google.com/docs/firestore/solutions/role-based-access
https://www.toptal.com/firebase/role-based-firebase-authentication
Feel free to open issues and provide feedback.