18

I was searching for an easy way to integrate Facebook PHP SDK with Laravel 5.4. Essentially, I wanted to make it available as a service within my Laravel app. Of course there is SammyK/LaravelFacebookSdk on github. But for some reason I didn't want to use it. I felt the setup was adding another layer that I would have to understand, and work within the constraints.

There is also Laravel's own Socialite package. But this is essentially for simple authentication only. If I want to upload photos, comments, make batch requests, these are not possible. Socialite does not use Facebook's PHP SDK as a dependency. It uses Guzzle package to make direct API requests for authentication only.

Since there is no simple SO answer to directly integrating Facebook SDK in least number of steps, I thought I'd write this.

Adam Ranganathan
  • 1,691
  • 1
  • 17
  • 25
  • For social media login releted stuff in laravel best plugin available 'laravel/socialite' also displayed in laravel official doc url : https://laravel.com/docs/5.0/authentication#social-authentication. –  Mar 22 '17 at 07:51
  • A year ago I created a simple package that just binded a Facebook class instance to the service container, allowing you to register your keys in your .env file and to do it "the laravel way". However, this is non tested code, developed on Laravel 5.3 (I think), and a year ago... You can check out the code if you are interested: https://github.com/ElMatella/SimpleFacebookLaravelSdk It is also available on packagist/composer – Hammerbot Mar 22 '17 at 08:18

1 Answers1

92

First, edit composer.json in the project's root folder to include the Facebook SDK:

{
  "require" : {
    "facebook/graph-sdk" : "~5.0"
  }
}

Next run composer update at shell prompt to pull in the sdk to the vendor folder.

Now we would like to use the Facebook SDK within our app as a service provider. But before doing that, let us setup our app_id, app_secret and default_graph_version which are parameters required while making requests to Facebook API. The app_id and app_secret can be obtained by registering at Facebook Developers website.

Once we have these credentials from Facebook, we would now edit .env file in the project's root folder. Add them to the end:

FACEBOOK_APP_ID=xxxxxxxxxxxxxxxx
FACEBOOK_APP_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
FACEBOOK_DEFAULT_GRAPH_VERSION=v2.8

Replace the xxx.. with the values provided to you. Note that the variable names are just my own creation. You can name them whatever you'd like. We would now have to use these variables to setup a separate config file. We need to do this so that we can use Laravel's config() helper function to retrieve the values wherever we want within the app. So let's create facebook.php in the config folder and add the following:

<?php

return [
    'config' => [
        'app_id' => env('FACEBOOK_APP_ID', null),
        'app_secret' => env('FACEBOOK_APP_SECRET', null),
        'default_graph_version' => env('FACEBOOK_DEFAULT_GRAPH_VERSION', 'v2.8'),
    ],
];

With this simple setup we can now call config('facebook.config') from anywhere in the app. It would return the array along with the relevant values matched from the .env file.

Now let us set this up as a service provider so that we don't have to retrieve these credentials and build new Facebook object every time we are making a call to the Facebook API.

In Laravel 5.4, open the file app\Providers\AppServiceProvider.php. If you don't have this file or want to make a separate one, then you could create a new service provider at shell:

php artisan make:provider FacebookServiceProvider

We can now edit FacebookServiceProvider.php in the Providers folder instead. The only difference is that we need to register this in the our config/app.php file. You would add the following at the end of the $providers array:

App\Providers\FacebookServiceProvider::class,

To continue with the relevant code, in either AppServiceProvider.php or our new FacebookServiceProvider.php we first include: use Facebook\Facebook; at the top. Then within the register() method add the following:

$this->app->singleton(Facebook::class, function ($app) {
            return new Facebook(config('facebook.config'));
        });

You might notice that I am binding the class as a singleton since for my app I would like to reuse the same object from the service container. There are other types of bindings provided by Laravel that you might want to check out.

The whole code would look like below (I am using AppServiceProvider.php):

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Facebook\Facebook;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton(Facebook::class, function ($app) {
            return new Facebook(config('facebook.config'));
        });
    }
}

That's about it. We now have Facebook available as a service to the app. We can now inject the facebook object wherever we would like to use it within our app. From here on one can simply follow instructions from Facebook documentation to call their API.'

Extra Stuff, just as an example:

Before proceeding, I'd like to mention that I found the series of posts written by the folks at Symfony to be very helpful to understand the concepts of Service Container and Dependency Injection. You can find them here

Let's now try to do a basic operation such as retrieving a person's name from facebook. In order to get information about a facebook user we would need to call the Facebook API by sending basic parameters: user's facebook id along with an access token. Let's call these uid and access_token respectively. You would need to retrieve these using one of Facebook's methods:

  1. FacebookRedirectLoginHelper - when you want facebook authentication to happen from server side.
  2. FacebookCanvasHelper - for client side canvas based app authentication
  3. FacebookJavaScriptHelper - for client side javascript authentication

You can set up the kind of authentication you want by following the steps provided in Facebook's getting started guide.

My app is pretty simple and so I went with client side javascript authentication. I also use jquery alongside. Since I use Laravel's blade engine, my javascript was embedded in the view file directly so that I could include Laravel's csrf_token() and use other helper functions such as url(). The client side javascript looks like below. Remember to replace appId with your values and save the file as login.blade.php.

<html>
<head>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<button id="btn-login" type="button" class="btn btn-primary btn-lg">
    <span> Login with Facebook</span>
</button>

<script>
$(document).ready(function() {
    $.ajaxSetup({ cache: true }); // since I am using jquery as well in my app
    $.getScript('//connect.facebook.net/en_US/sdk.js', function () {
        // initialize facebook sdk
        FB.init({
            appId: 'xxxxxxxxxxxxxxxx', // replace this with your id
            status: true,
            cookie: true,
            version: 'v2.8'
        });

        // attach login click event handler
        $("#btn-login").click(function(){
            FB.login(processLoginClick, {scope:'public_profile,email,user_friends', return_scopes: true});  
        });
    });

// function to send uid and access_token back to server
// actual permissions granted by user are also included just as an addition
function processLoginClick (response) {    
    var uid = response.authResponse.userID;
    var access_token = response.authResponse.accessToken;
    var permissions = response.authResponse.grantedScopes;
    var data = { uid:uid, 
                 access_token:access_token, 
                 _token:'{{ csrf_token() }}', // this is important for Laravel to receive the data
                 permissions:permissions 
               };        
    postData("{{ url('/login') }}", data, "post");
}

// function to post any data to server
function postData(url, data, method) 
{
    method = method || "post";
    var form = document.createElement("form");
    form.setAttribute("method", method);
    form.setAttribute("action", url);
    for(var key in data) {
        if(data.hasOwnProperty(key)) 
        {
            var hiddenField = document.createElement("input");
            hiddenField.setAttribute("type", "hidden");
            hiddenField.setAttribute("name", key);
            hiddenField.setAttribute("value", data[key]);
            form.appendChild(hiddenField);
         }
    }
    document.body.appendChild(form);
    form.submit();
}

</script>
</body>
</html>

If your app has a different set of permissions needed from user, then edit the scope field. For all available facebook permissions see here.

So basically, my code has a login button and when user clicks it, the javascript sdk kicks in and pops up a login window. Once user logs in, I post the data back to server as if a form is being posted.

Now back to server side, since we have uid and access_token we can store these to database and then make a simple call to Facebook API from our server. Set up a route in routes/web.php to receive the form data:

Route::post('login', 'FacebookUser@store');

Make FacebookUser controller at shell:

php artisan make:controller FacebookUser

And the controller's code is as follows:

<?php

namespace App\Http\Controllers;

use Request;
use App\User; // you need to define the model appropriately
use Facebook\Facebook;

class FacebookUser extends Controller
{
    public function store(Facebook $fb) //method injection
    {
        // retrieve form input parameters
        $uid = Request::input('uid');
        $access_token = Request::input('access_token');
        $permissions = Request::input('permissions');

        // assuming we have a User model already set up for our database
        // and assuming facebook_id field to exist in users table in database
        $user = User::firstOrCreate(['facebook_id' => $uid]); 

        // get long term access token for future use
        $oAuth2Client = $fb->getOAuth2Client();

        // assuming access_token field to exist in users table in database
        $user->access_token = $oAuth2Client->getLongLivedAccessToken($access_token)->getValue();
        $user->save();

        // set default access token for all future requests to Facebook API            
        $fb->setDefaultAccessToken($user->access_token);

        // call api to retrieve person's public_profile details
        $fields = "id,cover,name,first_name,last_name,age_range,link,gender,locale,picture,timezone,updated_time,verified";
        $fb_user = $fb->get('/me?fields='.$fields)->getGraphUser();
        dump($fb_user);
    }    
}

Note that by using setDefaultAccessToken() the $fb object can be retrieved from service container in any part of subsequent processing of app's codebase. $fb can be used directly to make a Facebook API request. No need to build $fb again using app_id, app_secret and no need to set access token again for the current user during the current request-response lifecycle. This is because $fb is singleton and so the same object is returned when service container is called for Facebook service during current request-response lifecycle.

If you are unable to do method injection to get the $fb object, then there are other ways of resolving it. My personal favourite is to use the resolve() helper since it works irrespective of object context or static function in a class.

$fb = resolve('Facebook\Facebook');
Adam Ranganathan
  • 1,691
  • 1
  • 17
  • 25
  • 6
    This answer is underrated – Hammerbot Mar 25 '17 at 13:53
  • $token is not defined – Hashan Nov 28 '17 at 14:35
  • @HashanKanchana, thanks for pointing that. That should have been $access_token, which I have changed now – Adam Ranganathan Dec 10 '17 at 01:01
  • Can we run this on our local server? – Zain Farooq Feb 27 '18 at 11:42
  • @ZainFarooq, yes you can run it locally as long as there is internet connection to call facebook apis. In fact, the code above was tested locally. – Adam Ranganathan Feb 28 '18 at 06:56
  • Thank you very much, Should I make virtual host on xampp? – Zain Farooq Feb 28 '18 at 07:11
  • @ZainFarooq, yes you can make a virtual host on xampp. A better way for Laravel would be to use [Homestead](https://laravel.com/docs/5.6/homestead) – Adam Ranganathan Feb 28 '18 at 07:24
  • I am superadmin of a laravel project , i set one of the users logged in with facebook as admin (role = admin) i want each time this user is logged to get the pages he manages and store the informations . note that i'm the one who created the app here (https://developers.facebook.com/) is that possible ? – Youssef Boudaya Mar 27 '18 at 08:25
  • @YoussefBoudaya, you'd have to request manage_pages permission. Please see: https://developers.facebook.com/docs/facebook-login/permissions/#reference-manage_pages – Adam Ranganathan Mar 27 '18 at 09:30
  • " Unable to load this URL: The domain of this URL is not registered in those of the application. To import this URL, add all the domains and subdomains of your application to the Domains field of the application settings." i'm getting this error while trying to login ! – Youssef Boudaya Mar 27 '18 at 10:05
  • @YoussefBoudaya, login to developers.facebook.com => hover on My Apps on top right => click on your app => click on settings in left column => click Basic => check if you've entered correct value in App Domains – Adam Ranganathan Mar 28 '18 at 05:21
  • this is the redirect uri show in the oauth dialog &redirect_uri=http%3A%2F%2Fstaticxx.facebook.com%2Fconnect%2Fxd_arbiter%2Fr%2Fnj6HSwERpgK.js%3Fversion%3D42%23cb%3Df9662cd322fb0c%26domain%3D127.0.0.1%26origin%3Dhttp%253A%252F%252F127.0.0.1%252Ff371c8925d0b414%26relation%3Dopener%26frame%3Df3bad1ab656aa44 what should i use as valid oauth url ? – Youssef Boudaya Apr 30 '18 at 11:09
  • @Sriram I need to use Facebook login with API application. Should I do anything than this for implement Facebook login correctly? – Suraj Malinga May 26 '18 at 08:22
  • @SurajMalinga, if authentication is your only requirement then you can use Laravel's Socialite package. If you also need to do multiple API transactions with Facebook such as uploading images, posting content, accessing pages, etc. then you can follow the instructions mentioned in this post. – Adam Ranganathan May 28 '18 at 04:14
  • This is great except for one huuuuge omission. When trying to do a login via OAuth, it throws an error about missing 'state' values in the session. – ortonomy May 13 '19 at 14:01
  • Thanks a lot for such detailed answer! Just a small clarification: `composer update` will update all of the project's dependencies and that isn't probably what most people want. You should either add the `facebook/graph-sdk` requirement to `composer.json` file and run the `composer install` commando or you can directly install it directly by running `composer require facebook/graph-sdk`. – juangiordana Jun 19 '19 at 20:20