Secure your laravel API with Google Firebase Auth
Adding the Middleware to your Laravel API will ensure that access is only granted by 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. Using laravel-firebase instead of firebase-tokens removes the need for a service provider since it is already included in laravel-firebase. Since that package depends on firebase-php, you can also use all the feature firebase-php 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 Laravel auth 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 inside Laravel
use Illuminate\Support\Facades\Auth;
class SomeClass
{
public function bar()
{
//Check if logged in and retrieve user object and uid using Laravel Auth Facade
$isLoggedIn = Auth::guard('api')->check();
$userObject = Auth::guard('api')->user();
$uid = Auth::guard('api')->id();
//Alternatively, use Laravel 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/React 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 on the firebase end, 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 property where we assign roles is referred to as custom claims in the following documentation.
https://firebase.google.com/docs/auth/admin/custom-claims
https://firebase.google.com/docs/firestore/solutions/role-based-access
https://firebase-php.readthedocs.io/en/5.x/user-management.html#custom-user-claims
https://www.toptal.com/firebase/role-based-firebase-authentication
Feel free to open issues and provide feedback.