Login to your website via Facebook using JavaScript and PHP


Facebook PHP SDK v.4.0 Facebook JavaScript SDK v.2.0 PHP JavaScript Authentication AJAX

If you've ever tried developing your own CMS or custom web application, you'd already know a login system is not that trivial, specially from a security point of view. Instead of creating our own, why not relying on the best known social network?

This is exactly what I've done for a recent project. I've used the facebook login button in order to have an on-page asynchronous button which callbacks an AJAX call on login and logout to a PHP page, which will take the facebook session, validate the data and eventually login or logout the user.

You may ask me why I didn't rely on the JavaScript API only, passing data to the PHP page. It's fairly easy to reply to this question though: security. The JavaScript API allows you to retrieve any kind of data relative to your account, but it's not secure if you want to pass the data from the client to the server, because the server can't simply trust something sent by "someone" (nothing prevents the user to send fake data). Thus this is the following procedure you'll see in this article:

  1. setup a login/logout facebook button which will ask the user some permissions in order to access part of his/her account (i.e. name, gender, email, id, etc) using the Facebook Javascript SDK v.2.0.
  2. once the user logs in or out, set or unset a cookie with the facebook session string, which will later be used by the PHP script to hook the same session (with the same permissions).
  3. create a PHP script which looks for the facebook session cookie and, using the Facebook PHP SDK v.4.0.
  4. the same PHP script will validate the session (if the cookie is set) and retrieve the data, or unset the session otherwise.

Prerequisites

  1. Basic knowledge of JavaScript and PHP (object-oriented)
  2. jQuery for the AJAX call
  3. A Facebook application correctly setup. See here for more information.
  4. PHP 5.4 or greater

1. The JavaScript part

:::JavaScript
function appPropagation() {
    "use strict";

    $.ajax({
        url: 'login.php',
        dataType: 'json',
        success: function (data) {
            console.log(data);
                // do whatever you want.
                // data.status: bool, true => login
                // [data.message: string]
                // [data.more: string]
        }
    });
}

This function will simply do the AJAX call. No data is passed by because all we need is the cookie we'll set in another part of the code. If you don't want to use cookies, you can pass the facebook session string as a POST data.

:::JavaScript
function login() {
    "use strict";

    document.cookie = 'fb_token=' + FB.getAuthResponse().accessToken;
    appPropagation();
}

This function is called when the user authenticates via Facebook and allows the application to retrieve the data you requested via the permissions (see later). FB.getAuthResponse().accessToken gets the facebook session string. In this case we're creating a cookie that will expire when the user closes the browser. After that, we'll do the AJAX call which will register the user in the system. Note though that I had to manually set the fb_token cookie as it seems the SDK is not doing it for me (see later).

:::JavaScript
function logout() {
    "use strict";

    document.cookie = 'fb_token=; expires=Thu, 01 Jan 1970 00:00:01 GMT;';
    appPropagation();
}

If the user is not logged in via facebook or denied the access to the application, this function will be fired. As you can see we're destroying the cookie (expires in the past), then we'll do the AJAX call which will unset the session, logging out from the system.

:::JavaScript
function loginStatusChangeCallback(response) {
    "use strict";

    if (response.status === 'connected') {
        login();
    } else {
        logout();
    }
}

This function will be fired every time the user changes his/her facebook login status. If he/she's connected, login, otherwise logout.

:::JavaScript
function checkLoginState() {
    "use strict";

    FB.getLoginStatus(function (response) {
        loginStatusChangeCallback(response);
    });
}

This function is used to... well, check the login status. It will be called once per page load (see later). It is important to call it like this, because it's fired by the facebook sdk, too.

:::JavaScript
window.fbAsyncInit = function () {
    "use strict";

    FB.init({
        appId      : 'your_app_id_here',
        cookie     : true,
        xfbml      : true,
        version    : 'v2.0'
    });

    checkLoginState();

};

This function will be fired once the facebook SDK loads. As you can see it is used to setup the SDK. You have to insert your appId. The cookie parameter should be used to store the facebook session, even if I haven't figured out how to properly let it work. xfbml is used to parse the DOM searching for the facebook special tags and render them. The last parameter, version, specifies the SDK version to use. As of May, 2014 version 2.0 is the last available to the public. Once setup, this function will also check the login status (so that if the user already gave the permissions to the application and first connects to the website, he/she'll be automatically logged in).

:::JavaScript
(function (d, s, id) {
    "use strict";

    var js, fjs = d.getElementsByTagName(s)[0];
    if (d.getElementById(id)) {
        return;
    }
    js = d.createElement(s);
    js.id = id;
    js.src = "//connect.facebook.net/en_US/sdk.js";
    fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));

This is simply the default snippet of code the Facebook SDK website tells you to write in your document.

2. HTML

This is rather simple: put the following code somewhere in your HTML in order to show the login/logout facebook button:

:::HTML
<fb:login-button autologoutlink="true" scope="public_profile,email" onlogin="checkLoginState();"></fb:login-button>

autologoutlink lets the button switch from "login" to "logout" depending on the login status. This can be overwritten inserting a string inside the tag. The scope attributes allows the SDK to retrieve certain data without requiring the access to the whole profile. In this case we're asking the user to share the account's public profile and the email. You can find a list of scopes on the facebook's developer website. The onlogin attribute fires the javascript every time a user changes the status.

3. PHP

The following code is straight forward and commented. Call this script 'login.php' (or whatever URL you've set in the $.ajax call).

:::PHP
// use some namespaces
use Facebook\FacebookSession;
use Facebook\FacebookRequest;
use Facebook\GraphObject;
use Facebook\GraphUser;

// used to return json-encoded data
$obj = new StdClass();
// default status: true (success)
$obj->status = true;

// the facebook token cookie is not set
if(!isset($_COOKIE['fb_token'])) {
    // logout
    // unset all the session's variables
    session_unset();

    $obj->status = false;
    $obj->message = 'Logout';
    die(json_encode($obj));
}

// I'm already logged in
if($_SESSION['user_id'] !== false) {
    $obj->message = 'Already logged in';
    die(json_encode($obj));
}

// load the facebook SDK (v.4.0)
// see https://github.com/facebook/facebook-php-sdk-v4

// yes, you need all of them.
require_once('facebook/FacebookSession.php');
require_once('facebook/FacebookRedirectLoginHelper.php');
require_once('facebook/FacebookRequest.php');
require_once('facebook/FacebookResponse.php');
require_once('facebook/FacebookSDKException.php');
require_once('facebook/FacebookRequestException.php');
require_once('facebook/FacebookOtherException.php');
require_once('facebook/FacebookAuthorizationException.php');
require_once('facebook/GraphObject.php');
require_once('facebook/GraphUser.php');
require_once('facebook/GraphSessionInfo.php');

FacebookSession::setDefaultApplication('facebook_app_id_here', 'facebook_app_secret_here');

// bind the JavaScript SDK session token with the PHP SDK
$session = new FacebookSession($_COOKIE['fb_token']);

// in case someone manually changed the cookie
// or the session expired...
try {
    $session->validate();
} catch (FacebookRequestException $ex) {
    $obj->status = false;
    $obj->message = 'Invalid facebook session';
    $obj->more = $ex->getMessage();
    die(json_encode($obj));
} catch (\Exception $ex) {
    $obj->status = false;
    $obj->message = 'Graph API error';
    $obj->more = $ex->getMessage();
    die(json_encode($obj));
}

// session ok, retrieve data
try {
    $request = new FacebookRequest($session, 'GET', '/me');
    // this means: retrieve a GraphObject and cast it as a GraphUser (as /me returns a GraphUser object)
    $me = $request->execute()->getGraphObject(GraphUser::className());
} catch (FacebookRequestException $ex) {
    $obj->status = false;
    $obj->message = 'Facebook Request error';
    $obj->more = $ex->getMessage();
    die(json_encode($obj));
} catch (\Exception $ex) {
    $obj->status = false;
    $obj->message = 'Generic Facebook error';
    $obj->more = $ex->getMessage();
    die(json_encode($obj));
}

// setup the user object
$user = new StdClass();
$user->facebookId = $me->getId();
$user->name = $me->getName();
$user->firstName = $me->getFirstName();
$user->lastName = $me->getLastName();
$user->email = $me->getProperty('email');   // some properties don't have a specific method
$user->gender = $me->getProperty('gender'); // hint: var_dump($me)
$user->locale = $me->getProperty('locale');

// insert here the object in your database (insert on key exists update, for example)
// PDO, MySQLi, MongoClient...

// insert the user data in the session
// you can alternatively serialise the object and save it all in one $_SESSION variable
$_SESSION['user_id'] = $me->getId(); // the facebook user's id
$_SESSION['user_name'] = $user->name; // short of firstname lastname
$_SESSION['user_firstname'] = $user->firstName;
$_SESSION['user_lastname'] = $user->lastName;
$_SESSION['user_locale'] = $user->locale; // may be used to integrate a multi-language content system

$obj->message = 'Logged in';
echo json_encode($obj);

Now the system is complete. Once you login or logout via facebook, the $_SESSION will be updated accordingly. As it's an AJAX call, you may decide to load user-related data via another AJAX call, or refresh the page. In this particular case, you'll probably want to differentiate the "already logged in" status from the "logged in" one (actually the two cases always return data.status = true, you may set them to data.status = 1 or 2, altering the $obj->status attribute in the PHP script)

- 17th May 2014

> back