Social Logins in PHP with HybridAuth
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.