Login (and register) with Facebook and Ion Auth

A lot of people asked (including a very generous donor called Federico), so why not try to do it?

I must start by telling you that this may not be the best way of doing it but it is a basic one…

So let’s start. In order to start doing our Facebook login we first need to take a look at the documentation from Facebook (https://developers.facebook.com/docs/facebook-login). This documentation says that in order to login with Facebook we first need to set up an “app” with an ID and a secret key (https://developers.facebook.com/docs/facebook-login/web). We do this by going to our App Dashboard (https://developers.facebook.com/apps/). In there we can create a “Demo” app, which is good for our testing needs.

Calling the Facebook SDK for PHP

Calling the Facebook SDK for PHP should be an easy task. We do this by using Composer (if you don’t know how or where to install composer, go to this tutorial – http://avenir.ro/codeigniter-tutorials/codeigniter-with-composer/ ).

First, setting up the Composer

Assuming you have Composer installed, and do not want to read all that tutorial, you can create a composer.json file from zero. In your terminal (or console, or whatever its name is…), you should go to the application directory, and type “composer init”. This will open a Composer config generator which will guide you to creating the composer.json file. For the moment just refuse to install any dependencies.

The end result should be a composer.json file that looks more or less like this:

{
    "name": "avenirer/application",
    "authors": [
        {
            "name": "Avenirer",
            "email": "avenir.ro@gmail.com"
        }
    ],
    "require": {}
}

Now, in your terminal, type “composer install”. This should create the vendor directory and the autoload file.

Loading the composer autoloader

Once we have the autoload.php inside our vendor directory, we must ask CodeIgniter to load it. I do this by changing the config.php file inside the application/config directory (but you can load it any way you want…)

In your config.php, look for a line with $config[‘composer_autoload’]. After that line, add the following line:

require_once APPPATH.'vendor/autoload.php';

Cool. Now we have CodeIgniter with Composer.

Installing our Facebook SDK for PHP

Now we can use Composer to install Facebook SDK for PHP. We do this by simply using the terminal (I sure hope you remained inside the application directory) and typing “composer require facebook/php-sdk-v4“.

Cool. As you can see, now the Facebook SDK is added to our application/vendor directory and also the composer.json file is looking a bit different…

{
    "name": "avenirer/application",
    "authors": [
        {
            "name": "Avenirer",
            "email": "avenir.ro@gmail.com"
        }
    ],
    "require": {
        "facebook/php-sdk-v4": "^5.2"
    }
}

Setting up the SDK

Now, that we have Facebook SDK installed and we have an ID and an App Secret Key we can set the SDK to work with our application.

We don’t need to remember the ID and App secret key, so why not just save them inside our config.php file.

$config['facebook_app_id'] = 'YOUR_APP_ID';
$config['facebook_app_secret'] = 'YOUR_FACEBOOK_APP_SECRET';

The Facebook object

Considering we will use Facebook login inside most of our application, we might as well create a Facebook object inside the MY_Controller file. The added lines to the file should look like this:

class MY_Controller extends CI_Controller
{
  public $fb; // this will be the object that we will use across the application
  function __construct()
  {
    parent::__construct();
    // now we only need to build the object...
    $this->fb = new Facebook\Facebook([
      'app_id' => $this->config->item('facebook_app_id'),
      'app_secret' => $this->config->item('facebook_app_secret'),
      'default_graph_version' => 'v2.5'
    ]);
  }
}

 

You must understand that I’ve only wrote THE ADDED LINES AND NOT THE ENTIRE MY_Controller, which we’ve created in the previous tutorials.

Making the login button (or rather link…)

First of all we need to know where will a visitor be sent after he agrees to login with Facebook. So let’s assume we will create a facebook() method in our User.php file, where the user will be redirected after trying to login with Facebook. In order for this to happen we will need to tell Facebook API to redirect the users. So in our view (I will use a “sidebar_view”, assuming the users will see the “Login with Facebook” button on every page of our site…) will will create a “Login with Facebook” button.

<?php
$helper = $this->fb->getRedirectLoginHelper();
$permissions = ['public_profile','email']; // these are the permissions we ask from the Facebook user's profile

echo anchor($helper->getLoginUrl('http://YOURSITE.COM/user/facebook', $permissions),'Login with Facebook');
?>

 

Maybe the code needs a little explanation (besides the fact that you need to change YOURSITE.COM with the address you need)… In order for us to allow the users to login with Facebook, we need to create a link that will tell Facebook where to redirect the user after a un/successful login took part (or a callback method, as Facebook puts it… https://developers.facebook.com/docs/php/howto/example_facebook_login). As you can see we will pass the permissions needed for our application to allow the user to login (namely email… you could of course only ask for the public_profile in order to allow the user to login, as you can find the email from there, but I wanted to make sure I get what I wanted).

The login processing

Now, returning to our User.php file, we need to create a method called facebook(). You can of course name it whatever you want, but make sure you also change the url inside the Facebook SDK for the login button.

 

public function facebook()
{
  $helper = $this->fb->getRedirectLoginHelper();
  try
  {
    $accessToken = $helper->getAccessToken();
  }
  catch(Facebook\Exceptions\FacebookResponseException $e)
  {
    // When Graph returns an error
    echo 'There was an error while trying to login using Facebook: ' . $e->getMessage();
    exit;
  }
  catch(Facebook\Exceptions\FacebookSDKException $e)
  {
    // When validation fails or other local issues
    echo 'Facebook SDK returned an error: ' . $e->getMessage();
    exit;
  }

  if (isset($accessToken))
  {
    $this->fb->setDefaultAccessToken($accessToken);
    try
    {
      $response = $this->fb->get('/me?fields=id,name,email');
      $user = $response->getGraphUser(); // we retrieve the user data
    }
    catch(Facebook\Exceptions\FacebookResponseException $e)
    {
      // When Graph returns an error
      echo 'Could not retrieve user data: ' . $e->getMessage();
      exit;
    }
    catch(Facebook\Exceptions\FacebookSDKException $e)
    {
      // When validation fails or other local issues
      echo 'Facebook SDK returned an error: ' . $e->getMessage();
      exit;
    }
  }
  else
  {
    echo 'oups... where is the access token???';
  }
}

Now, that we have the user’s data, especially the email, we should allow the user to login. But we can’t, as the Ion_Auth doesn’t allow us to login a user with only the email. So what do we do?

Create a User_model.php

Up until this point we didn’t work directly with the users table data, so there was no point in creating a User_model.php, but now it seems that we need to create it. So inside our application/models we should create a file named User_model.php, and in it we should create a method called login_with_facebook() that will simply look for the email address inside the table and create the needed session variables for the Ion Auth to think it has a logged in user:

 

<?php
defined('BASEPATH') OR exit('No direct script access allowed');

class User_model extends CI_Model
{
  public function __construct()
  {
    parent::__construct();
  }
  
  public function login_with_facebook($email)
  {
    $this->db->where('email',$email);
    $this->db->limit(1);
    $users = $this->db->count_all_results('users'); // are there any users with that email address?
    if(!isset($users) || $users<1)
    {
      return FALSE;
    }
    else
    {
      $user = $this->db->where(array('email'=>$email))->limit(1)->get('users')->row();
      $_SESSION['identity'] = $user->username; // if you've set up email as the login identity column, you shouls use $user->email in here...
      $_SESSION['username'] = $user->username;
      $_SESSION['email'] = $user->email;
      $_SESSION['user_id'] = $user->id;
      $_SESSION['old_last_login'] = $user->last_login;
      return TRUE;
    }
  }
}

Cool. Now, if there is an user with that particular email in our users table, that user should be logged in. If not, the model will return FALSE;

Returning to our User controller…

Now, returning to our facebook() method of our User controller, if the $user was retrieved successfully , we can call the User_model and try to login the user:

 

//we do not actually need to verify if the user email address is correct... but we should make sure
if($this->form_validation->valid_email($user['email']))
{
  $this->load->model('user_model');
  if($this->user_model->login_with_facebook($user['email']))
  {
    redirect('dashboard');
  }
  else
  {
    redirect('user/login');
  }
}

Let’s see the facebook() method again…

public function facebook()
{
  $helper = $this->fb->getRedirectLoginHelper();
  try
  {
    $accessToken = $helper->getAccessToken();
  }
  catch(Facebook\Exceptions\FacebookResponseException $e)
  {
    // When Graph returns an error
    echo 'There was an error while trying to login using Facebook: ' . $e->getMessage();
    exit;
  }
  catch(Facebook\Exceptions\FacebookSDKException $e)
  {
    // When validation fails or other local issues
    echo 'Facebook SDK returned an error: ' . $e->getMessage();
    exit;
  }

  if (isset($accessToken))
  {
    $this->fb->setDefaultAccessToken($accessToken);
    try
    {
      $response = $this->fb->get('/me?fields=id,name,email');
      $user = $response->getGraphUser(); // we retrieve the user data
    }
    catch(Facebook\Exceptions\FacebookResponseException $e)
    {
      // When Graph returns an error
      echo 'Could not retrieve user data: ' . $e->getMessage();
      exit;
    }
    catch(Facebook\Exceptions\FacebookSDKException $e)
    {
      // When validation fails or other local issues
      echo 'Facebook SDK returned an error: ' . $e->getMessage();
      exit;
    }

    //we do not actually need to verify if the user email address is correct... but we should make sure
    if($this->form_validation->valid_email($user['email']))
    {
      $this->load->model('user_model');
      if($this->user_model->login_with_facebook($user['email']))
      {
        redirect('dashboard');
      }
      else
      {
        redirect('user/login');
      }
    }
  }
  else
  {
    echo 'oups... where is the access token???';
  }
}

What about “auto”-registering

“But what if we want the user to be registered automatically when he wants to login with Facebook?” you may ask. So… you want the users that try to login with Facebook to be automatically registered and then logged in…

In order to do this we need to[paywall] modify the login_with_facebook() method of our User_model.php. Besides receiving the email, the method can also retrieve the user’s username:

public function login_with_facebook($email, $username)
{
  $this->db->where('email',$email);
  $this->db->limit(1);
  $users = $this->db->count_all_results('users');
  if(!isset($users) || $users<1)
  {
    $this->load->helper('string');
    $password = random_string('alnum',10); // we create a random password for the user...
    $register_id = $this->ion_auth->register($username,$password,$email,array(),array('2'));
    if($register_id)
    {
      $this->ion_auth->activate($register_id);
      $this->ion_auth->login($username,$password, TRUE);
    }
  }
  else
  {
    $user = $this->db->where(array('email'=>$email))->limit(1)->get('users')->row();			
    $_SESSION['identity'] = $user->username;
    $_SESSION['username'] = $user->username;
    $_SESSION['email'] = $user->email;
    $_SESSION['user_id'] = $user->id; //everyone likes to overwrite id so we'll use user_id
    $_SESSION['old_last_login'] = $user->last_login;
  }
  return TRUE;
}

Now, in our controller, we simply call the model’s method:

$this->user_model->login_with_facebook($user['email'], $user['name']

And that’s it.[/paywall] NOTE THAT I HAVEN’T REALLY TRIED ALL THAT YOU’VE SEEN, SO YOU MIGHT ENCOUNTER SOME PROBLEMS. IF THIS HAPPENS, PLEASE DO WRITE A COMMENT BELOW. IF IT DOESN’T, YOU CAN ALWAYS BUY ME A COFFEE (or a gift… who knows?…)

Leave a Reply

Your email address will not be published. Required fields are marked *

No spam? * Time limit is exhausted. Please reload CAPTCHA.