How to integrate Firebase with Laravel and set up authentication?
Learn how to seamlessly integrate Firebase with Laravel to enhance your web applications. This guide covers step-by-step instructions for setting up Firebase, configuring Laravel, and leveraging Firebase's authentication feature.
Since we're using Firebase, we need to create a custom authentication process for login, registration, email verification, and password reset. Let's start by connecting our Laravel project to the Firebase project.
Note: If you're unsure how to create a Firebase project and retrieve configuration keys, please refer to our post for detailed instructions.
We will use two packages for this integration. To install them, please run the following commands.
First, install 'google/cloud-firestore'
Source (GitHub) : https://github.com/kreait/laravel-firebase.git
After installing 'google/cloud-firestore', install 'kreait/firebase-php'
Source (GitHub) : https://github.com/kreait/firebase-php.git
Note : The gRPC extension is required for this packages, so make sure to install and enable it.
// First un this command
composer require google/cloud-firestore
// After installing command above install this
composer require kreait/laravel-firebase
We'll need a custom helper file to handle our Firebase operations. If you're unsure how to create a custom helper, check out our post on how to do it.
"files": [
"app/Services/firebase.php"
]
Now, open "app/Services/firebase.php" to begin working on the connection. Also, ensure that you paste the service JSON file into "app/Services/" with name "service.json".
<?php
use Kreait\Firebase\Factory;
if (!function_exists('firebaseDb'))
{
function firebaseDb()
{
$db = (new Factory)->withServiceAccount(__DIR__ . '/service.json')->createFirestore()->database();
return $db;
}
}
The code above connects the database using service account, while the code below establishes the Firebase authentication connection.
if (!function_exists('firebaseAuth'))
{
function firebaseAuth()
{
$db = (new Factory)->withServiceAccount(__DIR__ . '/service.json')->createAuth();
return $db;
}
}
To create a controller please execute the provided command.
php artisan make:controller Firebase/AuthController
Since we won't be using SQL, the default auth routes aren't useful anymore. However, we can still use 'GET' routes like 'register' and 'login'. So, let's create 'POST' routes to handle Firebase processing.
use App\Http\Controllers\Firebase\AuthController as FirebaseAuthController;
//LOGIN & REGISTRATION
Route::post('firebase/login', [FirebaseAuthController::class, 'login'])->name('firebase.login');
Route::post('firebase/registration', [FirebaseAuthController::class, 'registration'])->name('firebase.registration');
//REST PASSWORD
Route::post('firebase/send-rest-mail', [FirebaseAuthController::class, 'sendRestMail'])->name('firebase.send-rest-mail');
1. Head to firebase console to create database and table : console.firebase.google.com
2. Click on 'Firestore Database'
3. Click on 'Create Database.' and complete the process of creating database.
4. Start with test mode and change it to production mode when project is completed.
5. Create 'users' table
6. Add fields to collection / table. (You can delete this later)
Note : Password field is not required, And it should be replaced by 'uid' with data type string.
We'll create a function called 'registration' in 'Firebase/AuthController.php' to handle registration process.
public function registration(Request $request)
{
// Validate the incoming request data
$validated = $request->validate([
'name' => 'required',
'email' => 'required|email',
'password' => 'required|min:6|confirmed',
]);
$storeData = [];
try
{
// Check if a user with the same email already exists in the Firestore database
$checkExistingRecord = firebaseDb()->collection('users')
->where('email', '=', $request->email)
->documents()
->size();
// If the email already exists, return with an error message
if ($checkExistingRecord > 0)
{
return back()->withInput()->withErrors(['email' => 'Email already exists']);
}
// Create a new user with Firebase Auth
$newUser = firebaseAuth()->createUser([
'displayName' => $request->name,
'email' => $request->email,
'password' => $request->password,
'status' => true,
]);
// Reference to the new user document in Firestore
$user = firebaseDb()->collection('users')->document($newUser->uid);
// Fetch the last record to determine the next 'sr_no'
$lastRec = firebaseDb()->collection('users')
->orderBy('sr_no', 'DESC')
->limit(1)
->documents()
->rows();
// Prepare data to store in Firestore (excluding password for security)
$storeData += [
'uid' => $newUser->uid,
'name' => $request->name,
'email' => $request->email,
'status' => true,
'sr_no' => count($lastRec) > 0 ? $lastRec[0]['sr_no'] + 1 : 1,
];
// Store user data in Firestore
$user->set($storeData);
// Redirect to the login route with a success message
return redirect()->route('login')->with('success', __('Register successfully'));
}
catch (\Exception $exception)
{
// Redirect back with an error message in case of an exception
return back()->withInput()->withErrors(['email' => $exception->getMessage()]);
}
}
Since the register, login, and other blade files are already created when we install the auth composer, we only need to update the form action (route) in the form. So, go to 'resources/views/auth/register.blade.php'.
Again let's head to 'Firebase/AuthController.php' and create a function name 'login' to handle login process, Also don't forget to change form action (route) in 'resources/views/auth/login.blade.php'.
public function login(Request $request)
{
// Validate the incoming request data
$validated = $request->validate([
'email' => 'required|email',
'password' => 'required',
]);
try
{
// Sign in the user with email and password using Firebase Auth
$signInResult = firebaseAuth()->signInWithEmailAndPassword($request->email, $request->password);
// Get the ID token string from the sign-in result
$idTokenString = $signInResult->idToken();
// Verify the ID token
$verifiedIdToken = firebaseAuth()->verifyIdToken($idTokenString);
// Extract the user ID from the verified ID token
$userID = $verifiedIdToken->claims()->get('user_id');
// Get user data from the sign-in result
$userData = $signInResult->data();
// Create new claims for the user
$newClaims = [
'uid' => $userID,
'name' => $userData['displayName'],
'email' => $userData['email'],
];
// Set custom user claims in Firebase Auth
firebaseAuth()->setCustomUserClaims($userID, $newClaims);
// Store the ID token and user data in the session
session()->put('idToken', $idTokenString);
session()->put('auth-data', $newClaims);
// Redirect to the home route after successful login
return redirect()->route('home');
}
catch (\Exception $e)
{
// Redirect back with an error message if an exception occurs
return back()->withInput()->withErrors(['email' => str_replace("_", " ", $e->getMessage())]);
}
}
Feel free to add custom claims as needed and include the required data in the 'auth-data' session to display the authenticated user's information throughout the site.
To log out, simply destroy the session and redirect back to the login page. You can also use the default logout route, as it destroys all sessions, including the Firebase sessions we created.
With the login,registration and logout processes complete, we can now move on to the password reset process.
In 'Firebase/AuthController.php' and create a function name 'sendRestMail' to handle password reset process, Again don't forget to change form action (route) in 'resources/views/auth/passwords/email.blade.php'.
public function sendRestMail(Request $request)
{
$validated = $request->validate([
'email' => 'required|email',
]);
$actionCodeSettings = [
'continueUrl' => route('login'),
'dynamicLinkDomain' => null,
'handleCodeInApp' => false,
'androidPackageName' => null,
'androidInstallApp' => false,
'androidMinimumVersion' => null,
'iOSBundleId' => null,
];
$email = $request->email;
try
{
firebaseAuth()->sendPasswordResetLink($email,$actionCodeSettings);
return redirect()->back()->with('status',__('Reset password mail sent successfully'));
}
catch (\Exception $exception)
{
return back()->withInput()->withErrors(['email' => $exception->getMessage()]);
}
}
Firebase will manage the entire password reset process, including sending a reset link to the user's email, handling the reset in their system, and redirecting to the login page. You can also customize the email template in the Firebase console.
Next, we need to create middleware to manage the login session. To do this, run the command below.
php artisan make:middleware FirebaseAuth
Now a middleware named 'FirebaseAuth' should've been created in 'app/Http/Middleware/FirebaseAuth.php', So lets open it and update the code, We will verify 'idToken', If verified then continue to next page else redirect to login page.
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class FirebaseAuth
{
public function handle(Request $request, Closure $next): Response
{
// Retrieve the ID token from the session
$idTokenString = session()->get('idToken');
// Check if the ID token exists in the session
if (!empty($idTokenString))
{
try
{
// Verify the ID token using Firebase Auth
firebaseAuth()->verifyIdToken($idTokenString);
// If verification is successful, proceed with the next middleware/request handler
return $next($request);
}
catch (\Exception $exception)
{
// If verification fails, redirect to the login route
return redirect()->route('login');
}
}
else
{
// If no ID token is found in the session, redirect to the login route
return redirect()->route('login');
}
}
}
Our middleware is now ready for use. First, we'll assign it in 'app/Http/Middleware/Kernel.php'
protected $middlewareAliases = [
'firebase_auth' => \App\Http\Middleware\FirebaseAuth::class,
Let's set up a few more routes to handle profile updates and password changes.
Route::group(['middleware' => ['firebase_auth']], function ()
{
Route::get('profile', [FirebaseAuthController::class, 'profile'])->name('profile');
Route::post('update-profile', [FirebaseAuthController::class, 'updateProfile'])->name('update.profile');
Route::post('update-password', [FirebaseAuthController::class, 'updatePassword'])->name('update.password');
});
Navigate to 'Firebase/AuthController.php' to create the respective functions.
public function profile()
{
$claims = session()->get('auth-data');
$uid = $claims['uid'];
$auth_data = firebaseAuth()->getUser($uid);
$snapshot = firebaseDb()->collection('users')->document($uid)->snapshot();
return view('users.profile',compact('snapshot','auth_data'));
}
Displaying data using an array is quite basic, so let's move straight to the profile update function.
public function updateProfile(Request $request)
{
// Validate the incoming request data
$validator = \Validator::make(
$request->all(),
[
'name' => 'required',
'email' => 'required',
]
);
// If validation fails, redirect back with the first error message
if ($validator->fails())
{
$messages = $validator->getMessageBag();
return redirect()->back()->with('error', $messages->first());
}
// Retrieve authenticated user data from the session
$claims = session()->get('auth-data');
$uid = $claims['uid'];
$auth_data = firebaseAuth()->getUser($uid);
$storeArray = [];
$newClaims = [];
// Check if the email has been changed
if ($auth_data->email != $request->email)
{
// Check if the new email already exists in the database
$checkExistingRecord = firebaseDb()->collection('users')
->where('uid', '!=', $uid)
->where('email', '=', $request->email)
->documents()
->size();
// If the email already exists, return with an error message
if ($checkExistingRecord > 0)
{
return redirect()->back()->with('error', 'Email already exists');
}
try
{
// Update the user's email in Firebase Auth
firebaseAuth()->changeUserEmail($uid, $request->email);
}
catch (\Exception $exception)
{
// If updating the email fails, return with an error message
return redirect()->back()->with('error', $exception->getMessage());
}
}
// Reference to the user's document in Firestore
$user = firebaseDb()->collection('users')->document($uid);
// Prepare data to update in Firestore
array_push($storeArray,
['path' => 'name', 'value' => $request->name],
['path' => 'email', 'value' => $request->email],
);
// Prepare new claims to set for the user
$newClaims += [
'name' => $request->name,
'email' => $request->email,
];
// Update the user in Firebase Auth with the new claims
firebaseAuth()->updateUser($uid, $newClaims);
// Merge the new claims with the existing claims
$saveClaims = array_merge($claims, $newClaims);
// Set the updated claims as custom user claims in Firebase Auth
firebaseAuth()->setCustomUserClaims($uid, $saveClaims);
// Update the session data with the new claims
session()->put('auth-data', $saveClaims);
// Update the user document in Firestore with the new data
$user->update($storeArray);
// Redirect back with a success message
return redirect()->back()->with('success', __('Profile updated successfully'));
}
Now that our profile update function is complete, let's move on to the change password function. Unlike traditional password update functions, we cannot include a "match password" feature. Therefore, we should implement other types of verification for the password update. You can use email verification and ask for a relogin, or use email or mobile OTP based on your requirement. While I am not using any type of verification here, it is essential to implement one.
public function updatePassword(Request $request)
{
// Retrieve authenticated user data from the session
$claims = session()->get('auth-data');
$uid = $claims['uid'];
// Validate the incoming request data
$validator = \Validator::make(
$request->all(),
[
'password' => [
'required',
'min:6',
'confirmed',
],
]
);
// If validation fails, redirect back with the first error message
if ($validator->fails())
{
$messages = $validator->getMessageBag();
return redirect()->back()->with('error', $messages->first());
}
try
{
// Update the user's password in Firebase Auth
firebaseAuth()->changeUserPassword($uid, $request->password);
}
catch (\Exception $exception)
{
// If updating the password fails, redirect back with an error message
return redirect()->back()->with('error', $exception->getMessage());
}
// Redirect back with a success message after password update
return redirect()->back()->with('success', __('Password updated successfully'));
}
Now we've completed the entire authentication process:
1. Login
2. Registration
3. Password Reset
4. Profile Update
5. Password Update
Note: The only thing pending is image upload and update. That's a different module and requires knowledge of the storage bucket, so we'll cover it in the next post.