PHP
Article

Social Logins in PHP with HybridAuth

By Collins Agbonghama

A trend in many of today’s websites is a feature that allows users to sign in via their social network accounts. A classic example is the SitePoint community where users have the option to use their Facebook, Twitter, Google, Yahoo or GitHub account to log in without having to register an account.

In this tutorial, we will be learning about HybridAuth – a PHP library that takes the pain out of building a social login feature.

HybridAuth acts as an abstract API between your application and the various social APIs and identity providers.

Installation

The recommended way to install HybridAuth is via Composer. We’ll also use Slim as a foundation for our sample app.

{
    "require": {
        "slim/slim": "2.*",
        "hybridauth/hybridauth": "2.3.0"
    }
}

Using HybridAuth for Social Logins

To use HybridAuth, copy the config.php and index.php (HybridAuth Endpoint) files in /vendor/hybridauth/hybridauth/hybridauth to your project’s root folder.

Rename the index.php file to hybrid.php because index.php will be used by Slim framework for our demo application logic.

Populate the config.php file with your application’s (e.g. Facebook, Twitter application) credentials.

For example, If you want users to sign in to your website via Facebook, Google, and Twitter; your config file would look pretty much like this. My application URL is http://slim.local.

return 
	[
	    "base_url"   => "http://slim.local/",
	    "providers"  => [
	        "Google"   => [
	            "enabled" => true,
	            "keys"    => [ "id" => "", "secret" => "" ],
	        ],
	        "Facebook" => [
	            "enabled"        => true,
	            "keys"           => [ "id" => "", "secret" => "" ],
	            "trustForwarded" => false
	        ],
	        "Twitter"  => [
	            "enabled" => true,
	            "keys"    => [ "key" => "", "secret" => "" ]
	        ],
	    ],
	    "debug_mode" => true,
	    "debug_file" => "bug.txt",
	];

Note: The base_url parameter must point to the HybridAuth Endpoint file which is hybrid.php in our case.

See HybridAuth Configuration docs for more information.

Next, require the vendor autoloader and instantiate the class.

require 'vendor/autoload.php';
$hybridauth = new Hybrid_Auth( 'config.php' );

Use the authenticate method to authenticate users with a given provider.

For Facebook:

$adapter = $hybridauth->authenticate( "Facebook" );

For Twitter:

$adapter = $hybridauth->authenticate( "Twitter" );

For Google:

$adapter = $hybridauth->authenticate( "Google" );

The parameter that is passed to authenticate() must match the provider array key in config.php file.

After authentication, use the getUserProfile() method to retrieve the profile data of the user.

$user_profile = $adapter->getUserProfile();

The $user_profile variable will be an object that consists of the returned profile data of the user.

More Social Providers

To add more providers to the existing ones e.g. GitHub, copy the GitHub.php file from vendor/hybridauth/hybridauth/additional-providers/hybridauth-github/Providers to a location in your application (provider directory in our case). Use the provider wrapper to load the file where path is the path to the GitHub file and class the name of its PHP class.

"Github"   => [
    "enabled" => true,
    "keys"    => [
        "id"     => "",
        "secret" => ""
    ],
    "wrapper" => [ "path" => "providers/GitHub.php", "class" => "Hybrid_Providers_GitHub" ]
]

Authenticate the user with GitHub with HybridAuth’s autheniticate() method like so:

$adapter = $hybridauth->authenticate( "Github" );

Social Login Implementation

Normally, every website with a login and registration system uses the user’s email address or username for identifying and logging them into their account. If you intend to implement a social login feature, it is recommended not to use the user’s username or email for authentication.

One reason against this practice is that Twitter, for example, does not return the email address of a user authenticated by them. That is, the profile data returned does not contain the user’s email.

Most (if not all) social providers such as Facebook, Twitter, Google, LinkedIn and even GitHub include a unique user identification number returned after authorization.

Instead of using the user’s email for logging them into their account, use the identifier returned by the social providers as follows: create a user account if the user doesn’t have an account or log the user in to the site if he has an account.

Coding a Demo Application

We will be building a simple web application using the Slim PHP framework to demonstrate a real-world example of how social login with HybridAuth could be implemented.

I assume you have HybridAuth and Slim framework installed. If otherwise, see the installation guide above.

Application structure

|-scr/
|----App_Model.php
|-templates/
|----login.php
|----welcome.php
|-vendor/
|-composer.json
|-config.php
|-hybrid.php
|-index.php
|-.htaccess

Here is the SQL for the database table.

CREATE TABLE IF NOT EXISTS `users` (
`id` int(10) NOT NULL AUTO_INCREMENT,
PRIMARY KEY  (id),
  `identifier` varchar(50) NOT NULL,
UNIQUE KEY `identifier` (`identifier`),
  `email` varchar(50) DEFAULT NULL,
  `first_name` varchar(20) DEFAULT NULL,
  `last_name` varchar(20) DEFAULT NULL,
  `avatar_url` varchar(255)
) ENGINE=InnoDB;

Coding the Application Model

All code for the application model should go into the App_Model.php file in src folder.

The file is namespaced Model followed by the class definition and constructor.

<?php
namespace Model;

class App_Model
{

    /** @var object Database connection */
    private $conn;

    /**
     * Instantiate the model class.
     *
     * @param object $db_connection DB connection
     */
    public function __construct(\PDO $db_connection)
    {
        $this->conn = $db_connection;
    }

The identifier_exists method returns true if an identifier (user identification number) already exists in the database or false otherwise.

/**
     * Check if a HybridAuth identifier already exists in DB
     *
     * @param int $identifier
     *
     * @return bool
     */
    public function identifier_exists($identifier)
    {
        try {
            $sql    = 'SELECT identifier FROM users';
            $query  = $this->conn->query($sql);
            $result = $query->fetchAll(\PDO::FETCH_COLUMN, 0);

            return in_array($identifier, $result);
        } catch ( \PDOException $e ) {
            die( $e->getMessage() );
        }

    }

The method register_user inserts the user profile data into the database.

/**
     * Save users record to the database.
     *
     * @param string $identifier user's unique identifier
     * @param string $email
     * @param string $first_name
     * @param string $last_name
     * @param string $avatar_url
     *
     * @return bool
     */
    public function register_user( $identifier, $email, $first_name, $last_name, $avatar_url )
    {
        try {
            $sql = "INSERT INTO users (identifier, email, first_name, last_name, avatar_url) VALUES (:identifier, :email, :first_name, :last_name, :avatar_url)";

            $query = $this->conn->prepare($sql);
            $query->bindValue(':identifier', $identifier);
            $query->bindValue(':email', $email);
            $query->bindValue(':first_name', $first_name);
            $query->bindValue(':last_name', $last_name);
            $query->bindValue(':avatar_url', $avatar_url);

            return $query->execute();
        } catch (\PDOException $e) {
            return $e->getMessage();
        }

    }

The login_user method when called, adds the created user session to a HybridAuth session (created on successful authorization of a user by a provider).

/**
     * Create user login session
     *
     * @param int $identifier
     */
    public function login_user($identifier)
    {
        \Hybrid_Auth::storage()->set('user', $identifier);
    }

The logout_user method removes or destroys the user’s session when the logout link is clicked.

/** Destroy user login session */
    public function logout_user()
    {
        \Hybrid_Auth::storage()->set( 'user', null );
    }

Finally, the getter methods return the user’’s first name, last name, email and avatar URL.

/**
     * Return user's first name.
     *
     * @param int $identifier
     *
     * @return string
     */
    public function getFirstName( $identifier )
    {
        if ( ! isset( $identifier )) {
            return;
        }
        $query = $this->conn->prepare( "SELECT first_name FROM users WHERE identifier = :identifier" );
        $query->bindParam( ':identifier', $identifier );
        $query->execute();
        $result = $query->fetch( \PDO::FETCH_NUM );

        return $result[0];
    }


    /**
     * Return user's last name.
     *
     * @param int $identifier
     *
     * @return string
     */
    public function getLastName( $identifier )
    {
        if ( ! isset( $identifier )) {
            return;
        }
        $query = $this->conn->prepare( "SELECT last_name FROM users WHERE identifier = :identifier" );
        $query->bindParam( ':identifier', $identifier );
        $query->execute();
        $result = $query->fetch( \PDO::FETCH_NUM );

        return $result[0];
    }

    /**
     * Return user's email address
     *
     * @param int $identifier
     *
     * @return string
     */
    public function getEmail( $identifier )
    {
        if ( ! isset( $identifier )) {
            return;
        }
        $query = $this->conn->prepare( "SELECT email FROM users WHERE identifier = :identifier" );
        $query->bindParam( ':identifier', $identifier );
        $query->execute();
        $result = $query->fetch( \PDO::FETCH_NUM );

        return $result[0];
    }


    /**
     * Return the URL of user's avatar
     *
     * @param int $identifier
     *
     * @return string
     */
    public function getAvatarUrl( $identifier )
    {
        if ( ! isset( $identifier )) {
            return;
        }
        $query = $this->conn->prepare( "SELECT avatar_url FROM users WHERE identifier = :identifier" );
        $query->bindParam( ':identifier', $identifier );
        $query->execute();
        $result = $query->fetch( \PDO::FETCH_NUM );

        return $result[0];
    }

Register a PSR-4 autoloader for the Model class by adding the code below to your composer.json file.

"autoload": {
        "psr-4": {"Model\\": "src/"}
    }

Run composer dump-autoload to re-generate the vendor/autoload.php file.

The Application Logic

Require the composer autoload file and instantiate Slim in the application index.php file.

require 'vendor/autoload.php';

$app = new \Slim\Slim();

A directory called templates to store all our template files is created and then registered or configured in Slim as follows:

$app->config(
    [
        'templates.path' => 'templates'
    ]
);

Create a Slim database singleton resource that will return the database connection instance when called.

// Set singleton value
$app->container->singleton( 'db', function () {
        try {
            $db = new PDO( 'mysql:host=localhost;dbname=hybridauth', 'slim', 'slim',
                [ \PDO::ATTR_PERSISTENT => false ] );
        } catch ( PDOException $e ) {
            die( 'Error!: ' . $e->getMessage() );
        }

        return $db;
    }
);

Another singleton resource that returns an instance of HybridAuth is also created.

$app->container->singleton( 'hybridInstance', function () {
    $instance = new Hybrid_Auth('config.php');

    return $instance;
} );

Instantiate the application model class by passing the database connection as a parameter.

$model = new \Model\App_Model( $app->db );

The authenticate function below when added as a parameter to a route, redirects users to the login page if they aren’t logged in.

$authenticate = function ( $app ) {
    return function () use ( $app ) {
        $app->hybridInstance;
        $session_identifier = Hybrid_Auth::storage()->get( 'user' );

        if (is_null( $session_identifier ) && $app->request()->getPathInfo() != '/login/') {
            $app->redirect( '/login/' );
        }
    };
};

Redirect all logged out users to the login page when they visit the application home or index page.

$app->get( '/', $authenticate($app) );

Below is the route definition of the social login links. I.e. when the link http://slim.local/login/facebook is clicked, the user is redirected to Facebook by HybridAuth for authorization. Same goes for Twitter http://slim.local/login/twitter, Google http://slim.local/login/google and every other supported provider.

$app->get( '/login/:idp', function ( $idp ) use ( $app, $model ) {
        try {
            $adapter      = $app->hybridInstance->authenticate( ucwords( $idp ) );
            $user_profile = $adapter->getUserProfile();

            if (empty( $user_profile )) {
                $app->redirect( '/login/?err=1' );
            }

            $identifier = $user_profile->identifier;

            if ($model->identifier_exists( $identifier )) {
                $model->login_user( $identifier );
                $app->redirect( '/welcome/' );
            } else {
                $register = $model->register_user(
                    $identifier,
                    $user_profile->email,
                    $user_profile->firstName,
                    $user_profile->lastName,
                    $user_profile->photoURL
                );

                if ($register) {
                    $model->login_user( $identifier );
                    $app->redirect( '/welcome/' );
                }

            }

        } catch ( Exception $e ) {
            echo $e->getMessage();
        }
    }
);

The HybridAuth authenticate() method is called to redirect the user to the given social provider.

On successful authorization, the variable $user_profile is populated with the user profile data.

The identifier_exists() method is called to check if the user identifier exists in the database. If true, the user is logged in to the site. Otherwise, an account is created for the user and afterward, the user is logged in.

Here is the code for the logout route.

$app->get( '/logout/', function () use ( $app, $model ) {
        $app->hybridInstance;
        $model->logout_user();
        Hybrid_Auth::logoutAllProviders();
        $app->redirect( '/login/' );
    }
);

The logout_user method we talked about in the model class is called to destroy the user session and Hybrid_Auth::logoutAllProviders() is also called to log the user out of the connected provider.

The route for the welcome page where users are redirected to when they log in:

$app->get( '/welcome/', $authenticate( $app ), function () use ( $app, $model ) {
		$app->render( 'welcome.php', [ 'model' => $model ] );
	}
);

Finally, run the Slim application.

// app/index.php
$app->run();

See the application’s GitHub repo for the full source code.

Conclusion

In this article, we learned how to integrate a social login feature with a website using the powerful and robust HybridAuth PHP library.

If you have any questions or contributions, let us know in the comments.

Comments
Milan_Rukavina

Very useful article!

There's one thing which confuses me. In you code I see hybrid.php with
Hybrid_Endpoint::process();

and configs base_url points to this file. But in the text, nor in any other examples I could not find reference to this code.
Is this needed or not?

Thanks

collizo4sky

Hybrid_Endpoint::process(); is a core hybrid method that calls the appropriate methods to process a social login authentication.

Since we are routing the auth request to hybrid.php, the method need to be in the file. For instance, when you download the hybridauth library, all examples are routend to index.php. open the file and you will see it inside.

Oj_Obasi

Fatal error: Uncaught exception 'Hybrid_Exception' with message 'Oophs. Error!' in /home1/ericele/public_html/sitename.com/aaaaaslim/vendor/hybridauth/hybridauth/hybridauth/Hybrid/Endpoint.php:221 Stack trace: #0 /home1/ericele/public_html/sitename.com/aaaaaslim/vendor/hybridauth/hybridauth/hybridauth/Hybrid/Endpoint.php(124): Hybrid_Endpoint->authInit() #1 /home1/ericele/public_html/sitename.com/aaaaaslim/vendor/hybridauth/hybridauth/hybridauth/Hybrid/Endpoint.php(51): Hybrid_Endpoint->processAuthStart() #2 /home1/ericele/public_html/sitename.com/aaaaaslim/vendor/hybridauth/hybridauth/hybridauth/Hybrid/Endpoint.php(71): Hybrid_Endpoint->_construct(NULL) #3 /home1/ericele/publichtml/sitename.com/aaaaaslim/hybrid.php(11): Hybrid_Endpoint::process() #4 {main} thrown in /home1/ericele/public_html/sitename.com/aaaaaslim/vendor/hybridauth/hybridauth/hybridauth/Hybrid/Endpoint.php on line 221

avi_patil22

I liked this article however I dont use slim framework. Can you provide how to use hybridauth with simple PHP code ?

swader

It's almost the same - it's identical up until the "Application logic" part which will differ for every non-Slim app. That's where your own router and your own controller logic will be taking over, so we can't help with that part

Recommended
Sponsors
Because We Like You
Free Ebooks!

Grab SitePoint's top 10 web dev and design ebooks, completely free!

Get the latest in PHP, once a week, for free.