Symfony 3 : Login avec Facebook connect

Afin de faciliter la connexion des utilisateurs à votre site, et augmenter accessoirement le nombre d’inscrits, il est de plus en plus fréquent d’offrir la possibilité de le faire via le compte Facebook connect, Twitter ou Google+. En effet, tous ces réseaux sociaux permettent d’utiliser leurs api respectives pour se connecter à des sites tiers.

Pour ce faire, il existe un bundle très utilisé sur Symfony une panoplie de services et de fonctionnalités pour arriver à notre fin, il s’agit de HWIOAuthBundle qui supporte actuellement pas moins de 58 services différents:

  • 37signals,
  • Amazon,
  • Asana,
  • Auth0,
  • Azure,
  • Bitbucket,
  • Bitly,
  • Box,
  • BufferApp,
  • Clever,
  • Dailymotion,
  • Deezer,
  • DeviantArt,
  • Discogs,
  • Disqus,
  • Dropbox,
  • EVE Online,
  • Facebook,
  • FI-WARE,
  • Flickr,
  • Foursquare,
  • GitHub,
  • Google,
  • Hubic,
  • Instagram,
  • Itembase,
  • Jawbone,
  • JIRA,
  • LinkedIn,
  • Mail.ru
  • Odnoklassniki,
  • Office365,
  • PayPal,
  • QQ,
  • RunKeeper,
  • Salesforce,
  • Sensio Connect,
  • Sina Weibo,
  • Slack,
  • Soundcloud,
  • Spotify,
  • Stack Exchange,
  • Stereomood,
  • Strava,
  • Toshl,
  • Trakt,
  • Trello,
  • Twitch,
  • Twitter,
  • VKontakte,
  • Windows Live,
  • WordPress,
  • Wunderlist,
  • XING,
  • Yahoo,
  • Yandex,
  • Youtube

Avant de commencer, pour la suite de cet article on suppose que vous avez déjà mis en place une connexion à votre site en utilisant FOSUserBundle (plus de détails github et symfony).

Installation de HWIOAuthBundle

Il suffit de se connecter à la console et de taper la commande suivante :

composer require hwi/oauth-bundle php-http/guzzle6-adapter php-http/httplug-bundle

Une fois le téléchargement terminé, il faut modifier le fichier app/AppKernel.php en ajoutant les lignes

public function registerBundles()
{
    $bundles = [
    // ...
        new HWI\Bundle\OAuthBundle\HWIOAuthBundle(),
        new Http\HttplugBundle\HttplugBundle(),
    ];
    // ...
}

Par la suite, il nous faut importer les routes qui suivent dans le fichier app/config/routing.yml en veillant les importer avant les routes de FOSUserBundle et idéalement avant même vos propres routes, pour éviter les bugs.

#hwi_oauth
hwi_oauth_security:
    resource: "@HWIOAuthBundle/Resources/config/routing/login.xml"
    prefix: /connect

hwi_oauth_connect:
    resource: "@HWIOAuthBundle/Resources/config/routing/connect.xml"
    prefix: /connect

hwi_oauth_redirect:
    resource: "@HWIOAuthBundle/Resources/config/routing/redirect.xml"
    prefix:   /connect

facebook_login:
    path: /connect/check-facebook

Configuration de HWIOAuthBundle

Dans cet article, on va voir uniquement la connexion via Facebook connect, mai sachez pour pour les autres services, le traitement est presque le même.

Commençons par nous connecter à la console développeur de Facebook et commencer par créer une nouvelle application en renseignant le nom d’usage (nom de votre site)

Puis choisir Configurer du produit Facebook Login dans la liste qui s’affiche puis choisir Web comme plateforme de l’app.

Cliquer ensuite sur Paramètres en indiquer dans URI de redirection OAuth valides, l’url qui correspond, dans mon exemple il s’agit de https://www.site.fr/connect/check-facebook, pensez à changer www.site.fr pour le votre.

Attention : Depuis quelques temps, il n’est plus possible d’utiliser une url non sécurisée (https) pour les redirections

Une fois ces informations enregistrées, il faut aller sur les Paramètres (1), puis renseigner une URL pour la politique de confidentialité (2)  (obligatoire lors de la publication de l’application), puis copier les identifiants de l’app et la clé secrète qu’on va utiliser plus tard (3) et (4).

Maintenant nous allons modifier le fichier config.yml pour ajouter la configuration nécéssaire au bon fonctionnement de HWIOAuthBundle.

parameters:
    # ...
    facebook_client_id: "xxxxxxxxxxxxx"
    facebook_client_secret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

#...

hwi_oauth:
    connect:
        account_connector: my_user_provider
    firewall_names: [main]
    fosub:
        username_iterations: 30
        properties:
            facebook: facebook_id
    resource_owners:
        facebook:
            type:                facebook
            client_id:           "%facebook_client_id%"
            client_secret:       "%facebook_client_secret%"
            scope:               email
            infos_url:           "https://graph.facebook.com/me?fields=email,first_name,last_name,name,picture.type(square),id"
            paths:
                email:       email
                firstname:   first_name
                lastname:    last_name
                profilepicture: picture
            options:
                display: popup
            # Google Access
#            google:
#                type:                google
#                client_id:           [ClientID]
#                client_secret:       [ClientIDSecret]
#                scope:               "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile"
#                options:
#                    display: popup
#            # Twitter Access
#            twitter:
#                type:                twitter
#                client_id:           [ClientID]
#                client_secret:       [ClientIDSecret]
#                scope:               ""

Puis dans le fichier app/config/services.yml, on ajoute

services:
    # ...
    my_user_provider:
        class: UserBundle\Security\Core\User\FOSUBUserProvider
        arguments: ['@fos_user.user_manager',{facebook: facebook_id}]

Nous allons par la suite créer le fichier FOSUBUserProvider indiqué plus haut dans services.yml

<?php
namespace UserBundle\Security\Core\User;

use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
use HWI\Bundle\OAuthBundle\Security\Core\User\FOSUBUserProvider as BaseClass;
use Symfony\Component\Security\Core\User\UserInterface;

class FOSUBUserProvider extends BaseClass
{
    /**
     * {@inheritDoc}
     */
    public function connect(UserInterface $user, UserResponseInterface $response)
    {
        $property = $this->getProperty($response);
        $username = $response->getUsername();
        
        $service = $response->getResourceOwner()->getName();
        $setter = 'set'.ucfirst($service);
        $setter_id = $setter.'Id';
        $setter_token = $setter.'AccessToken';
        //On déconnecte le précedent utilisateur
        if (null !== $previousUser = $this->userManager->findUserBy(array($property => $username))) {
            $previousUser->$setter_id(null);
            $previousUser->$setter_token(null);
            $this->userManager->updateUser($previousUser);
        }
        //we connect current user
        $user->$setter_id($username);
        $user->$setter_token($response->getAccessToken());
        $this->userManager->updateUser($user);
    }

    /**
     * {@inheritdoc}
     */
    public function loadUserByOAuthUserResponse(UserResponseInterface $response)
    {
        $username = $response->getUsername();
        $user = $this->userManager->findUserBy(array($this->getProperty($response) => $username));
        // Si l'utilisateur n'existe pas
        if (null === $user) {
            $service = $response->getResourceOwner()->getName();
            $setter = 'set'.ucfirst($service);
            $setter_id = $setter.'Id';
            $setter_token = $setter.'AccessToken';
            // On créait le nouvel utilisateur ici
            $user = $this->userManager->createUser();
            $user->$setter_id($username);
            $user->$setter_token($response->getAccessToken());
            // On intègre les champs que l'on souhaite
            $user->setUsername($response->getFirstName().$response->getLastName());
            // Vous pouvez également récupérer la  photo du profil avec $response->getProfilePicture()['data']['url']
            $user->setEmail($response->getEmail());
            $user->setPassword($username);
            $user->setEnabled(true);
            $this->userManager->updateUser($user);
            return $user;
        }
        // Si l'utilisateur existe, on utilise la méthode parente
        $user = parent::loadUserByOAuthUserResponse($response);
        $serviceName = $response->getResourceOwner()->getName();
        $setter = 'set' . ucfirst($serviceName) . 'AccessToken';
        // On change le token
        $user->$setter($response->getAccessToken());
        return $user;
    }
}

Il faut ensuite apporter quelques modifications à notre entité UserBundle/Entity/User.php

    /**
     * @ORM\Column(name="facebook_id", type="string", length=255, nullable=true)
     */
    private $facebook_id;
    
    /**
     * @ORM\Column(name="facebook_access_token", type="string", length=255, nullable=true)
     */
    private $facebook_access_token;

// ...

    /**
     * Set facebook_id
     *
     * @param string $facebookId
     * @return User
     */
    public function setFacebookId($facebookId)
    {
        $this->facebook_id = $facebookId;
        
        return $this;
    }
    
    /**
     * Get facebook_id
     *
     * @return string
     */
    public function getFacebookId()
    {
        return $this->facebook_id;
    }
    
    /**
     * Set facebook_access_token
     *
     * @param string $facebookAccessToken
     * @return User
     */
    public function setFacebookAccessToken($facebookAccessToken)
    {
        $this->facebook_access_token = $facebookAccessToken;
        
        return $this;
    }
    
    /**
     * Get facebook_access_token
     *
     * @return string
     */
    public function getFacebookAccessToken()
    {
        return $this->facebook_access_token;
    }

La dernière modification va se faire sur le fichier security.yml

#...    
    firewalls:
        #...
        main:
            pattern:        ^/
            anonymous:      true
            provider:       main
            form_login:
                login_path: fos_user_security_login
                check_path: fos_user_security_check
                default_target_path: _homepage
            logout:
                path:       fos_user_security_logout

            oauth:
                resource_owners:
                    facebook:       "/connect/check-facebook"
                login_path:         /login
                failure_path:       /login

                oauth_user_provider:
                    service: my_user_provider

Tester le fonctionnement de HWIOAuthBundle

Maintenant qu’on a enfin pu terminer avec la configuration, on va pouvoir tester le bon fonctionnement de la connexion.

Accéder directement à l’url https://www.site.fr/connect (en utilisant votre propre url), vous trouverez un lien Facebook, cliquez dessus, il va vous rediriger pour vous autoriser l’application à se connecter à votre compte.

Voila, il ne vous reste plus maintenant qu’à personnaliser votre formulaire de connexion pour ajouter le bouton Connexion avec Facebook, il y a pleins de tutoriels qui vous proposent ça !