Laravel Firebase SNS Authentication — Complete Guide

GaonLabs
8 min readMay 6, 2020

--

If you liked this tutorial, we will post more and new tutorials at https://www.gaonlabs.com. Please visit the website to find more tutorials.

If you plan on developing your website with Laravel and want to use Firebase as your authentication mechanism, follow this guideline to authenticate a user using email, Facebook or Google.

Setup

Laravel Setup

Follow the guideline from Laravel official website to setup your Laravel environment. For a reference, I’m using version 7.6.2 in this tutorial.

Ngrok Setup

Unless you have a domain name which supports https endpoint, utilize ngrok which lets you create a https public endpoint to your server as Facebook does not support using a local ip address. Go to their website, download one for your environment.

Create your Laravel Project

Let’s create a Laravel project with built-in authentication mechanism.

laravel new auth-tutorial --auth && cd auth-tutorial

Add a Firebase Library

We will use a Firebase for Laravel library. Run

composer require kreait/laravel-firebase

Add the following in config/app.php .

<?phpreturn [
// ...
'providers' => [
// ...
Kreait\Laravel\Firebase\ServiceProvider::class,
],
// ...
];

Firebase Credential Setup

Firebase — Generate new private key

Go to Firebase console and create a new project. I named it laravel-tutorial. Go to Project Settings->Service accounts->Firebase Admin SDK. Click Generate new private key and save it under resources/credentials/firebase_credentials.json. Then in .env file, add a variable FIREBASE_CREDENTIALS=resources/credentials/firebase_credentials.json.

Run Laravel and Ngrok

Let’s run Laravel. I’ll run it on port 8080 but you can run it on any port you wish. Remember this port to use with ngrok.

php artisan serve --port=8080

Now, using ngrok you downloaded earlier, we will link the local port to a public endpoint.

./ngrok http 8080

You will see an endpoint like: https://abcd1234.ngrok.io. We will use this endpoint to set up and test your authentication. If you re-run ngrok, the endpoint will change which will require you to change the configuration on firebase again.

Ngrok

Visit your ngrok endpoint and see your basic Laravel page.

Firebase, Facebook, Google set-up

Facebook Login Setup

Go to Facebook’s developers page. Create a new app and Add Facebook Login product. Add Web platform. Under Products->Facebook Login->Settings, enable Client OAuth Login, Web OAuth Login, Enforce HTTPS, Use Strict Mode for Redirect URIs. Usually, these are enabled by default. We will come back to this page to fill Valid OAuth Redirect URIs with a value we will get from Firebase page. Go to Settings->Basic and get your App ID and App Secret. You will use this later in Firebase webpage.

Facebook Login System. We will provide firebase endpoint to the section pointed above.
Facebook — App ID and App Secret Location. You will use these in Firebase console.

Google Login Setup

You don’t need to do anything separate here.

Firebase Setup

Go to Firebase console and go to Authentication page. Click Sign-in method.

Enable Facebook. Input your App ID and App secret which you got from Facebook developer page from above step. Now, take a note of a URI in the pop-up. It looks like https://<project name>.firebaseapp.com/__/auth/handler. Copy this URI in Facebook developer console’s Facebook Login settings page under Valid OAuth Redirect URIs.

Firebase Facebook Authentication Configuration. You will need to copy App ID and App secret from Facebook page to here and copy the redirect URI to facebook page.

Enable Google. No additional configuration needed.

Enable Email/Password if you want to let users sign-up and use an email address.

Under Authorized domains, add your website endpoint. If you used ngrok, it will look like abcd1234.ngrok.io.

Now, go to Project Settings->General. Under Your apps, add a new Web apps. You will be given a Firebase SDK snippet. We will use this javascript code section later.

We will use this variable in our login page. Even though I blacked out my values, they are meant to be public.

Laravel Code Change

Modification to Users

We will re-use the Users class which Laravel already provided. The file is located at app/Users.php

<?phpnamespace App;use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable {
use Notifiable;
protected $fillable = [
'displayName', 'email', 'localId',
];
public function getAuthIdentifierName() {
return 'localId';
}
public function getAuthIdentifier(){
return $this->localId;
}
}

Create a Firebase User Provider

Create a user provider which will be used to authenticate the user by an identifier. Create a file under app/Firebase/FirebaseUserProvider.php

<?phpnamespace App\Firebase;use Illuminate\Contracts\Auth\Authenticatable as UserContract;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
use Kreait\Firebase\Auth as FirebaseAuth;
use App\User;
class FirebaseUserProvider implements UserProvider {
protected $hasher;
protected $model;
protected $auth;
public function __construct(HasherContract $hasher, $model) {
$this->model = $model;
$this->hasher = $hasher;
$this->auth = app('firebase.auth');
}
public function retrieveById($identifier) {
$firebaseUser = $this->auth->getUser($identifier);
$user = new User([
'localId' => $firebaseUser->uid,
'email' => $firebaseUser->email,
'displayName' => $firebaseUser->displayName
]);
return $user;
}
public function retrieveByToken($identifier, $token) {} public function updateRememberToken(UserContract $user, $token) {} public function retrieveByCredentials(array $credentials) {} public function validateCredentials(UserContract $user, array $credentials) {}}

Utilize the provider

Make a modification to app/Providers/AuthServiceProvider.php ‘s boot function.

<?phpnamespace App\Providers;use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use App\Firebase\Guard as FirebaseGuard;
use App\Firebase\FirebaseUserProvider;
class AuthServiceProvider extends ServiceProvider {
protected $policies = [
// 'App\Model' => 'App\Policies\ModelPolicy',
];
public function boot() {
$this->registerPolicies();
\Illuminate\Support\Facades\Auth::provider('firebaseuserprovider', function($app, array $config) {
return new FirebaseUserProvider($app['hash'], $config['model']);
});
}
}

Make an update to auth config file

Make the following modification to config/auth.php file.

<?phpreturn [
// ...
`guards` => [
`web' => [
'driver' => 'session',
'provider' => 'firebaseUser',
],
// ...
],
// ...
'providers' => [
// ...
'firebaseUser' => [
'driver' => 'firebaseuserprovider',
'model' => App\User::class,
],
],
// ...
];

Modification to LoginController

We will need to modify the provided LoginController file to utilize Firebase authentication. We will also implement the callback function which will verify and login the user into your website. The file is located under app/Http/Controllers/Auth/LoginController.php.

<?phpnamespace App\Http\Controllers\Auth;use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Kreait\Firebase\Auth as FirebaseAuth;
use Kreait\Firebase\Exception\FirebaseException;
use Illuminate\Validation\ValidationException;
use Auth;
use App\User;
class LoginController extends Controller {
use AuthenticatesUsers;
protected $auth;
protected $redirectTo = RouteServiceProvider::HOME;
public function __construct(FirebaseAuth $auth) {
$this->middleware('guest')->except('logout');
$this->auth = $auth;
}
protected function login(Request $request) {
try {
$signInResult = $this->auth->signInWithEmailAndPassword($request['email'], $request['password']);
$user = new User($signInResult->data());
$result = Auth::login($user);
return redirect($this->redirectPath());
} catch (FirebaseException $e) {
throw ValidationException::withMessages([$this->username() => [trans('auth.failed')],]);
}
}
public function username() {
return 'email';
}
public function handleCallback(Request $request, $provider) {
$socialTokenId = $request->input('social-login-tokenId', '');
try {
$verifiedIdToken = $this->auth->verifyIdToken($socialTokenId);
$user = new User();
$user->displayName = $verifiedIdToken->getClaim('name');
$user->email = $verifiedIdToken->getClaim('email');
$user->localId = $verifiedIdToken->getClaim('user_id');
Auth::login($user);
return redirect($this->redirectPath());
} catch (\InvalidArgumentException $e) {
return redirect()->route('login');
} catch (InvalidToken $e) {
return redirect()->route('login');
}
}
}

Here for login function, we take in the email and password and authenticate against Firebase users. If the login is successful, the user is logged in using Auth::login function. If not, then exception is thrown.

For handleCallback function, we will take a verification token from the frontend, which we will implement later, verify it against Firebase, and log the user using Auth::login.

Modification to Routes

We need to add a new route to the callback path. Add the following route to routes/web.php

Route::post('login/{provider}/callback', 'Auth\LoginController@handleCallback');

Modification to RegisterController

When the user tries to register through the website using email/password, we need to create a user through Firebase. The file is located at app/Http/Controllers/Auth/RegisterController.php

<?phpnamespace App\Http\Controllers\Auth;use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Kreait\Firebase\Auth as FirebaseAuth;
use Kreait\Firebase\Exception\FirebaseException;
class RegisterController extends Controller {
use RegistersUsers;
protected $auth;
protected $redirectTo = RouteServiceProvider::HOME; public function __construct(FirebaseAuth $auth) {
$this->middleware('guest');
$this->auth = $auth;
}
protected function validator(array $data) {
return Validator::make($data, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
]);
}
protected function register(Request $request) {
$this->validator($request->all())->validate();
$userProperties = [
'email' => $request->input('email'),
'emailVerified' => false,
'password' => $request->input('password'),
'displayName' => $request->input('name'),
'disabled' => false,
];
$createdUser = $this->auth->createUser($userProperties); return redirect()->route('login');
}
}

Modification to login page

We will make a modification to the login page. resources/views/auth/login.blade.php. Here, we will insert javascript to authenticate the user through firebase. Then, we will pass the verification token to the backend, which will verify it against firebase through LoginController ‘s handleCallback function, then log the user in. You can implement your own facebook/google login buttons. I used bootstrap-social.

After the included login form, insert the following

<div class="mb-0">
<a class="btn btn-block btn-social btn-facebook" onClick="socialSignin('facebook');">
<span class="fa fa-facebook"></span> Sign in with Facebook
</a>
<a class="btn btn-block btn-social btn-google" onClick="socialSignin('google');">
<span class="fa fa-google"></span> Sign in with Google
</a>
</div>
<form id="social-login-form" action="" method="POST" style="display: none;">
{{ csrf_field() }}
<input id="social-login-access-token" name="social-login-access-token" type="text">
<input id="social-login-tokenId" name="social-login-tokenId" type="text">
</form>

Before @endsection, add the following.

<script src="https://www.gstatic.com/firebasejs/7.14.0/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.14.0/firebase-auth.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
// Initialize Firebase
var config = {
// This is the variable you got from Firebase's Firebase SDK snippet. It includes values for apiKey, authDomain, projectId, etc.
};
firebase.initializeApp(config);
var facebookProvider = new firebase.auth.FacebookAuthProvider();
var googleProvider = new firebase.auth.GoogleAuthProvider();
var facebookCallbackLink = '/login/facebook/callback';
var googleCallbackLink = '/login/google/callback';
async function socialSignin(provider) {
var socialProvider = null;
if (provider == "facebook") {
socialProvider = facebookProvider;
document.getElementById('social-login-form').action = facebookCallbackLink;
} else if (provider == "google") {
socialProvider = googleProvider;
document.getElementById('social-login-form').action = googleCallbackLink;
} else {
return;
}
firebase.auth().signInWithPopup(socialProvider).then(function(result) {
result.user.getIdToken().then(function(result) {
document.getElementById('social-login-tokenId').value = result;
document.getElementById('social-login-form').submit();
});
}).catch(function(error) {
// do error handling
console.log(error);
});
}
</script>

That’s it. Now, you can authenticate the user using Facebook, Google or email/password. This tutorial gives you an idea of how you can use Firebase with Laravel. You may need to add custom parameters, additional security measures, etc. If you want to complete email/password set-up, you may need to implement your own email verfication/password reset mechanism or use Firebase’s built in functionality.

All the files can be found in Github here.

Thank you for reading!

Common Mistakes

  • When testing, make sure that your endpoint is to https://…/login. This is especially important when you are testing your Facebook login method. By default, Laravel is not generating https links.
  • By default, you cannot have two accounts with the same email address. For example, if you created an email/password account and try to login through Facebook which is registered with the same email address, it will fail. You can look read it from console log.
  • Make sure you update the Authorized domains if you restarted ngrok as the endpoint would have changed.

--

--