A lot of people suffer today the famous "Password Fatigue", even probably you. The visitors of a website are always looking for a simple way to do the things, and when we talk about logging into sites, the Social Login are the best option because the users can use their existing social accounts like Facebook, Google+, Twitter etc. This advantage, can boost conversions on your website by improving their User Experience. Many websites report that their target consumers prefer using Social Login instead of creating a new account on their site and that for good reason. For a Symfony Project, the HWIOAuthBundle is the most famous and best solution to achieve this task.
In this article you will learn how to allow your user to login into your app using a Social Network. Although you can configure in the same way any social network, we'll explain in this case with Github, Facebook, Google Plus and Stack Exchange.
Note
You can follow the same process if you want to add other Social Networks like Twitter etc. Or if you don't need a social network from the example, just skip it.
Requirements
Before proceeding, you will need to create in the fos_user
table of your database 2 columns of type string (Varchar 255) for every Social Network (resource owner) that you want to add. We'll set this step as a requirement, because you are the one that decides how to add the fields to the database. Some developers handle the database with some manager like PHPMyAdmin or other follows the symfony way by modifying the user.orm.yml
file and then building the database using php bin/console doctrine:schema:update --force
.
The 2 fields for every resource owners follow the next nomenclature: <social-network-name>_id
and <social-network-name>_access_token
. For example, with our 4 mentioned social networks we would have 8 new columns in the fos_user
table namely:
github_id
github_access_token
facebook
facebook_access_token
googleplus_id
googleplus_access_token
stackexchange_id
stackexchange_access_token
Once the columns exists, you will need obviously add the getters and setters of the field in the User class of your application:
/** @ORM\Column(name="github_id", type="string", length=255, nullable=true) */
protected $github_id;
/** @ORM\Column(name="github_access_token", type="string", length=255, nullable=true) */
protected $github_access_token;
/** @ORM\Column(name="facebook_id", type="string", length=255, nullable=true) */
protected $facebook_id;
/** @ORM\Column(name="facebook_access_token", type="string", length=255, nullable=true) */
protected $facebook_access_token;
/** @ORM\Column(name="googleplus_id", type="string", length=255, nullable=true) */
protected $googleplus_id;
/** @ORM\Column(name="googleplus_access_token", type="string", length=255, nullable=true) */
protected $googleplus_access_token;
/** @ORM\Column(name="stackexchange_id", type="string", length=255, nullable=true) */
protected $stackexchange_id;
/** @ORM\Column(name="stackexchange_access_token", type="string", length=255, nullable=true) */
protected $stackexchange_access_token;
public function setGithubId($githubId) {
$this->github_id = $githubId;
return $this;
}
public function getGithubId() {
return $this->github_id;
}
public function setGithubAccessToken($githubAccessToken) {
$this->github_access_token = $githubAccessToken;
return $this;
}
public function getGithubAccessToken() {
return $this->github_access_token;
}
public function setFacebookId($facebookID) {
$this->facebook_id = $facebookID;
return $this;
}
public function getFacebookId() {
return $this->facebook_id;
}
public function setFacebookAccessToken($facebookAccessToken) {
$this->facebook_access_token = $facebookAccessToken;
return $this;
}
public function getFacebookAccessToken() {
return $this->facebook_access_token;
}
public function setGoogleplusId($googlePlusId) {
$this->googleplus_id = $googlePlusId;
return $this;
}
public function getGoogleplusId() {
return $this->googleplus_id;
}
public function setGoogleplusAccessToken($googleplusAccessToken) {
$this->googleplus_access_token = $googleplusAccessToken;
return $this;
}
public function getGoogleplusAccessToken() {
return $this->googleplus_access_token;
}
public function setStackexchangeId($stackExchangeId) {
$this->stackexchange_id = $stackExchangeId;
return $this;
}
public function getStackexchangeId() {
return $this->stackexchange_id;
}
public function setStackexchangeAccessToken($stackExchangeAccessToken) {
$this->stackexchange_access_token = $stackExchangeAccessToken;
return $this;
}
public function getStackexchangeAccessToken() {
return $this->stackexchange_access_token;
}
If you set everything in order, you will be able to retrieve those properties from the User object and you can proceed to configure the HWIOAuthBundle.
Note
Remember that you need to clear the cache and logout from the current user when you modify the user class, otherwise when you add new fields, they won't be updated till the user logins again.
1. Install and enable HWIOAuthBundle
The first you need to do is to install the HWIOAuthBundle with composer using the following command:
composer require hwi/oauth-bundle
Alternatively, you can modify manually your composer.json
and set the bundle as a dependency:
{
"require": {
"hwi/oauth-bundle": "^0.5.3",
}
}
And finally install it using composer install
. Once the installation of the bundle finishes, don't forget to enable it in the AppKernel.php
file of your symfony app:
<?php
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Config\Loader\LoaderInterface;
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = [
// ..
new HWI\Bundle\OAuthBundle\HWIOAuthBundle(),
// ..
];
// ..
}
}
2. Create Developer Accounts on Social Networks
The most important point to allow that your user can login into your application with a Social Network, is that the third party service (Social Network) allow you to do it as well. For it, most of the services require that your application is registered in order to manage requests permissions etc.
For example, for Github you can register your application here, for Facebook here, for Stack Exchange here and for Google Plus Here. Once your application is registered, they will provide you the required OAuth tokens that will allow you to create request to their servers. The Github manager looks like:
Note
Besides in the settings of your custom application, you will need to provide the Authorization Callback URL that we'll define in the step #4. So create your application with this option empty and don't forget to update it once you are done with this tutorial.
In this case the URL will follow a pattern like https://yourwebsite/connect/check-<resource-owner-name>
., however you are free to change the callback URL as you follow this tutorial.
Almost all of the Resource Owner have at least 2 parameters namely client_id
and secret
. In this example, we are going to use the 4 previously mentioned Social Networks, so we'll need to register the following parameters in your app/config/config.yml
file. Is recommended to store your tokens within double quotes too e.g "your-token-here"
:
# app/config/config.yml
parameters:
# For Github you'll need the client_id and secret
github_client_id: <replace-with-your-github-client-id>
github_secret: <replace-with-your-github-secret>
# For Facebook you'll need the client_id and secret
facebook_client_id: <replace-with-your-facebook-client-id>
facebook_secret: <replace-with-your-facebook-secret>
# For Google+ you'll need the client_id and secret
googleplus_client_id: <replace-with-your-googleplus-client-id>
googleplus_secret: <replace-with-your-googleplus-secret>
# For Stack Exchange you'll need the client_id, secret and key
stackexchange_client_id: <replace-with-your-stackexchange-client-id>
stackexchange_secret: <replace-with-your-stackexchange-secret>
stackexchange_key: <replace-with-your-stackexchange-key>
These tokens will be used by the Resource Owners in the next step.
3. Configure HWIO and Resource Owner
Now that you have the rights to create requests to the desired Social Network servers, you need to create the local Resource Owners of every Social Network. Go to the config.yml
file of your Symfony Application and set the configuration (hwi_oauth
) of HWIOAuthBundle. Here is where you register new social networks with their respective access tokens:
# app/config/config.yml
hwi_oauth:
# Define which firewalls will be used for oauth
# Usually, its only the main, but you can add it if you have a custom one
firewall_names: ["main"]
fosub:
username_iterations: 30
# Define in which columns of the fos_user table will be stored
# the access token of every resource_owner
properties:
github: github_id
facebook: facebook_id
googleplus: googleplus_id
stackexchange: stackexchange_id
# Define the resource_owners that your user can use to login into your app
# Note that the client_id and client_secret and key values are symfony parameters
# stored too in the config.yml from the previous step !
resource_owners:
github:
type: github
client_id: "%github_client_id%"
client_secret: "%github_secret%"
scope: 'user:email,public_repo'
facebook:
type: facebook
client_id: "%facebook_client_id%"
client_secret: "%facebook_secret%"
infos_url: "https://graph.facebook.com/me?fields=id,name,email"
googleplus:
type: google
client_id: "%googleplus_client_id%"
client_secret: "%googleplus_secret%"
scope: "email profile"
stackexchange:
type: stack_exchange
client_id: "%stackexchange_client_id%"
client_secret: "%stackexchange_secret%"
options:
key: "%stackexchange_key%"
For more information about how the configuration of every resource owner works, refer to the official docs of HWIOAuthBundle here.
4. Configure Resource Owners Routes
Your user will need to access a route that identifies with which social network he wants to login. Go to the app/config/security.yml
file of your application and add the oauth
configuration for your firewall:
# app/config/security.yml
security:
# Modify firewalls
firewalls:
# Set the config on your firewall
main:
oauth:
# Declare the OAuth Callback URLs for every resource owner
# They will be added in the routing.yml file too later
resource_owners:
github: "/connect/check-github"
facebook: "/connect/check-facebook"
googleplus: "/connect/check-googleplus"
stackexchange: "/connect/check-stackexchange"
## Provide the original login path of your application (fosuserroute)
## and the failure route when the authentication fails.
login_path: /user/login
failure_path: /user/login
# Inject a service that will be created in the step #6
oauth_user_provider:
service: app.fos_user.oauth_provider
As next, proceed to add the routes of HWIOBundle and the resource owners to your app/config/routing.yml
file:
Note
As mentioned in the step 2, in the OAuth account manager of apps like Facebook, Github etc, you will need to provide the OAuth Callback URL. You can use the routes of this step to provide a route to the third party services e.g http://yoursite.com/connect/check-facebook
.
# app/config/routing.yml
hwi_oauth_redirect:
resource: "@HWIOAuthBundle/Resources/config/routing/redirect.xml"
prefix: /connect
hwi_oauth_connect:
resource: "@HWIOAuthBundle/Resources/config/routing/connect.xml"
prefix: /connect
hwi_oauth_login:
resource: "@HWIOAuthBundle/Resources/config/routing/login.xml"
prefix: /login
github_login:
path: /connect/check-github
facebook_login:
path: /connect/check-facebook
googleplus_login:
path: /connect/check-googleplus
stackexchange_login:
path: /connect/check-stackexchange
In this way, if the user wants to sign in to your application using his Facebook Account, you would only need to redirect him to the route http://yoursite.com/connect/check-facebook
.
5. Create Login and Register Manager
Of someway your application needs to receive information from the Social Network to register or to login. That's the function of the following FOSUBUserProvider class. By default it works without needing any modification. Once the user access the check route, the loadUserByOAuthUserResponse
function comes into action. If the user isn't registered with the Social Network Account on your application, it will create a new row on the fos_user table by default with a random username e.g 12345_<name-of-social-network>
and signs him automatically. If the user already exists, it will search the user by the field <social-network>_id
and will send the access token to retrieve information.
You are free to modify the class to set the fields you need, to set the username you need etc. In this case, we stored the class in the Entity folder of the AppBundle, but you can save it wherever you want:
<?php
// Change the namespace according to your project.
namespace AppBundle\Entity;
use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
use HWI\Bundle\OAuthBundle\Security\Core\User\FOSUBUserProvider as BaseClass;
use Symfony\Component\Security\Core\User\UserInterface;
// Source: https://gist.github.com/danvbe/4476697
class FOSUBUserProvider extends BaseClass {
public function connect(UserInterface $user, UserResponseInterface $response) {
$property = $this->getProperty($response);
$username = $response->getUsername();
// On connect, retrieve the access token and the user id
$service = $response->getResourceOwner()->getName();
$setter = 'set' . ucfirst($service);
$setter_id = $setter . 'Id';
$setter_token = $setter . 'AccessToken';
// Disconnect previously connected users
if (null !== $previousUser = $this->userManager->findUserBy(array($property => $username))) {
$previousUser->$setter_id(null);
$previousUser->$setter_token(null);
$this->userManager->updateUser($previousUser);
}
// Connect using the current user
$user->$setter_id($username);
$user->$setter_token($response->getAccessToken());
$this->userManager->updateUser($user);
}
public function loadUserByOAuthUserResponse(UserResponseInterface $response) {
$data = $response->getResponse();
$username = $response->getUsername();
$email = $response->getEmail() ? $response->getEmail() : $username;
$user = $this->userManager->findUserBy(array($this->getProperty($response) => $username));
// If the user is new
if (null === $user) {
$service = $response->getResourceOwner()->getName();
$setter = 'set' . ucfirst($service);
$setter_id = $setter . 'Id';
$setter_token = $setter . 'AccessToken';
// create new user here
$user = $this->userManager->createUser();
$user->$setter_id($username);
$user->$setter_token($response->getAccessToken());
//I have set all requested data with the user's username
//modify here with relevant data
$user->setUsername($this->generateRandomUsername($username, $response->getResourceOwner()->getName()));
$user->setEmail($email);
$user->setPassword($username);
$user->setEnabled(true);
$this->userManager->updateUser($user);
return $user;
}
// If the user exists, use the HWIOAuth
$user = parent::loadUserByOAuthUserResponse($response);
$serviceName = $response->getResourceOwner()->getName();
$setter = 'set' . ucfirst($serviceName) . 'AccessToken';
// Update the access token
$user->$setter($response->getAccessToken());
return $user;
}
/**
* Generates a random username with the given
* e.g 12345_github, 12345_facebook
*
* @param string $username
* @param type $serviceName
* @return type
*/
private function generateRandomUsername($username, $serviceName){
if(!$username){
$username = "user". uniqid((rand()), true) . $serviceName;
}
return $username. "_" . $serviceName;
}
}
6. Create the fos_user.oauth_provider service
In the security.yml
we have defined the oauth_user_provider
option with the app.fos_user.oauth_provider
service, that till now doesn't exist, therefore you need to create it. The service returns the FOSUBUserProvider class and as arguments the user manager of FOSUserBundle and the Resource Owners created in the step 3:
# app/config/services.yml
services:
app.fos_user.oauth_provider:
# Change the class according to the location of the FOSUBUserProvider class
class: AppBundle\Entity\FOSUBUserProvider
arguments:
# Inject as first argument the user_manager of FOSUserBundle
user_manager: "@fos_user.user_manager"
# An object/array with the registered Social Media from config.yml
user_response:
github: github_id
facebook: facebook_id
googleplus: googleplus_id
stackexchange: stackexchange_id
7. Test it !
If everything was configured correctly (and following the default configuration) you will able to access (login or register) via a Social Network accessing the following routes:
<!-- Remove app_dev.php from the URL if you aren't in DEV mode -->
<a href="/app_dev.php/connect/github">
Login with Github
</a>
<a href="/app_dev.php/connect/stackexchange">
Login with Stack Exchange
</a>
<a href="/app_dev.php/connect/facebook">
Login with Facebook
</a>
<a href="/app_dev.php/connect/googleplus">
Login with Google+
</a>
Here the user will be redirected to the Social Network grant page that asks the user if he really want to use his account to sign into another application. It's worth to say that you need to clear the cache and sign out from the current account to prevent any error.
Happy coding !