keyboard

Hugo Soltys

Symfony developer

Since 2013

Use Facebook or Google to login to your Symfony website with HWIOAuthBundle

Posted on by Hugo - 966 views - 0 comment


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.


Hugo Soltys

My name is Hugo, I'm 25 and I'm a Symfony developer since 2013. I love to create websites by myself to learn new technologies or increase my skills.
I love movies, books, music and video games. I also like to drink a few beers with my friends. I'm from Lille (France) and I currently work as Symfony developer at Decathlon since 2016. Before that, I worked as Symfony developer for the IT Room company, in Roubaix, France.