The authentication with Facebook or Google is pretty normal for the user today because it's useful and reassuring.
In most of the cases, the OAuth protocol is used to do this.
Learn how to implement a Symfony Facebook login or a Symfony Google login with the HWIOAuthBundle.
Today, everyone have a Facebook profile or a Google account. That's why it can be very useful to add a login feature that allows you users to authenticate with their social network profile.
HWIOAuthBundle allows you to implements this functionnality with a lot of providers (See the full list here), but in this tutorial we will focus on the two biggest : Facebook and Google.
NB : I will not copy the official documentation of the bundle so to install it follow the instructions available on the GitHub page.
CONFIGURING THE RESOURCE OWNERS
HWIOAuthBundle creates a dedicated service for each resource owner you want to use in your application. These resource owners will be used in the oauth firewall. The bundle ships several pre-configured resource owners that need only a little configuration.
In your config.yml
file, add the following configuration :
hwi_oauth:
firewall_names: [main]
fosub:
properties:
facebook: facebookId
google: googleId
resource_owners:
facebook:
type: facebook
client_id: your_facebook_client_id
client_secret: your_facebook_client_secret
options:
display: popup
auth_type: rerequest
csrf: true
google:
type: google
scope: "your wanted scope" # exemple : https://www.googleapis.com/auth/userinfo.email
client_id: your_google_client_id
client_secret: your_google_client_secret
CONFIGURING THE SECURITY LAYER
Now, in your security.yml
you will have to put the firewall configuration you will need to configure a login path for the resource owners you configured above.
Additionally you will need to point the oauth firewall to the appropriate service to use for loading users.
To do so, add this configuration :
#app/config/security.yml
security:
firewalls:
main:
pattern: ^/
oauth:
resource_owners:
facebook: "/secured/login_facebook"
google: "/secured/login_google"
login_path: fos_user_security_login
failure_path: fos_user_security_login
oauth_user_provider:
service: my.oauth_aware.user_provider.service
anonymous: true
Then configure your user provider service :
#app/config/services.yml
services:
my.oauth_aware.user_provider.service:
class: UserBundle\Security\Provider\MyFOSUBProvider
arguments:
- '@fos_user.user_manager'
- { facebook: facebookId, google: googleId }
- '@doctrine'
And don't forget to add in your routing.yml
file the resource_owners that you configured.
#app/config/routing.yml
facebook_login:
path: /secured/login_facebook
google_login:
path: /secured/login_google
ADD THE REQUIRED PROPERTIES TO YOUR USER CLASS
Once your users will be logged with their Facebook or Google account, you will have to store their identifier in your database.
So, in your User class add the two required fields google_id
and facebook_id
:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="user")
*/
class User
{
// ...
/**
* @ORM\Column(type="string", name="facebook_id", nullable=true)
*/
protected $facebookId;
/**
* @ORM\Column(type="string", name="google_id", nullable=true)
*/
protected $googleId;
/**
* @return mixed
*/
public function getFacebookId()
{
return $this->facebookId;
}
/**
* @param mixed $facebookId
*/
public function setFacebookId($facebookId)
{
$this->facebookId = $facebookId;
}
/**
* @return mixed
*/
public function getGoogleId()
{
return $this->googleId;
}
/**
* @param mixed $googleId
*/
public function setGoogleId($googleId)
{
$this->googleId = $googleId;
}
}
Then, update your database with the bin/console doctrine:schema:update --force
CREATE YOUR USER PROVIDER CLASS
Now that you have set up all the required configuration and added the required database fields, create a new class called MyFOSUBProvider
. In this class, create or update a user with all the informations provided by Facebook or Google in your database.
NB : The amount of informations you will receive from these websites will depend on the scope that you require. You can find a complete list of scopes for Facebook by clicking here and for Google by clicking here.
<?php
namespace AppBundle\Security\Provider;
use Doctrine\Bundle\DoctrineBundle\Registry;
use FOS\UserBundle\Model\UserManagerInterface;
use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
use HWI\Bundle\OAuthBundle\Security\Core\User\FOSUBUserProvider;
use AppBundle\Entity\User;
class MyFOSUBProvider extends FOSUBUserProvider
{
private $doctrine;
/**
* @param UserManagerInterface $userManager
* @param array $properties
* @param Registry $doctrine
*/
public function __construct(UserManagerInterface $userManager, array $properties, $doctrine)
{
parent::__construct($userManager, $properties);
$this->doctrine = $doctrine;
}
/**
* {@inheritdoc}
*/
public function loadUserByOAuthUserResponse(UserResponseInterface $response)
{
$username = $response->getUsername();
$property = $this->getProperty($response);
$user = $this->userManager->findUserBy(array($this->getProperty($response) => $username));
$email = $response->getEmail();
// check if we already have this user
$existing = $this->userManager->findUserBy(array('email' => $email));
if ($existing instanceof User) {
// in case of Facebook login, update the facebook_id
if ($property == "facebookId") {
$existing->setFacebookId($username);
}
// in case of Google login, update the google_id
if ($property == "googleId") {
$existing->setGoogleId($username);
}
$this->userManager->updateUser($existing);
return $existing;
}
// if we don't know the user, create it
if (null === $user || null === $username) {
/** @var User $user */
$user = $this->userManager->createUser();
$nick = "johndoe"; // to be changed
$user->setLastLogin(new \DateTime());
$user->setEnabled(true);
$user->setUsername($nick);
$user->setUsernameCanonical($nick);
$user->setPassword(sha1(uniqid()));
$user->addRole('ROLE_USER');
if ($property == "facebookId") {
$user->setFacebookId($username);
}
if ($property == "googleId") {
$user->setGoogleId($username);
}
}
$user->setEmail($response->getEmail());
$user->setFirstname($response->getFirstName());
$user->setLastname($response->getLastName());
$this->userManager->updateUser($user);
return $user;
}
}
CONFIGURE YOUR APP ON FACEBOOK AND GOOGLE
Now your app is programmatically configured to allow a connection with those two social medias, you must create your app on their developers portal.
See Facebook for Developers and Google APIs to create your app.
NB : You'll cannot test the Facebook or Google connection locally because you will have to set the origin host of your calls.
TEST YOUR INTEGRATION
Create a new TWIG template and add a Facebook login button and a Google login button to test your integration.
<div id="background">
<div id="page-title">
<h1>
Login
</h1>
</div>
<ul id="social-connect">
<li>
<a href="#" onclick="fb_login();">
Connect with Facebook
</a>
</li>
<li>
<a href="{{ path('hwi_oauth_service_redirect', {'service': 'google'}) }}" >
Connect with Google
</a>
</li>
</ul>
</form>
<div id="fb-root"></div>
<script
src="https://code.jquery.com/jquery-3.2.1.min.js"
integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
crossorigin="anonymous"></script>
<script>
window.fbAsyncInit = function() {
// init the FB JS SDK
FB.init({
appId : 'YOUR_APP_ID', // App ID from the app dashboard
channelUrl : '//mysite.com/channel.html', // Channel file for x-domain comms
status : true, // Check Facebook Login status
xfbml : true // Look for social plugins on the page
});
};
// Load the SDK asynchronously
(function(d, s, id){
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/all.js";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
function fb_login() {
FB.getLoginStatus(function(response) {
if (response.status === 'connected') {
// connected
document.location = "{{ url("hwi_oauth_service_redirect", {service: "facebook"}) }}";
} else {
// not_authorized
FB.login(function(response) {
if (response.authResponse) {
document.location = "{{ url("hwi_oauth_service_redirect", {service: "facebook"}) }}";
}
}, {scope: 'email'});
}
});
}
</script>
</div>
You have now a fully functionnal Facebook and Google authentication system to your Symfony application.
I hope this article helped you, if you have any question please leave a comment.
Hugo.