Cette article est très vieux et ne devrait pas être utilisé comme support pour votre travail.
Si vous affichez un formulaire de trois kilomètres de long à un utilisateur pour s’inscrire, il va vous faire une syncope tout de suite. C’est terrible, les statistiques le montrent, tout le monde à la flemme et du coup personne ne s’inscrit.
Mais proposez-lui une inscription en deux clics depuis un compte Google ou Facebook (ça tombe bien en plus il est déjà connecté sur son navigateur) et ça sera joie / bonheur / exaltation dans son cœur. Et vous, vous récupérerez un utilisateur de plus. Les fainéants sont légion et il nous faut toujours plus de monde sur nos applis !
Cependant, je vous vois venir avec votre remarque. En effet, certains vont prendre peur en voyant des réseaux sociaux et ne s’inscriront jamais chez vous. On va donc également proposer une alternative email classique.
Pour ce faire on va se baser sur deux bundles, le premier étant FOSUserBundle, poids lourd de la gestion d’utilisateurs dans le milieu symfony2. Le second HWIOAuthBundle permettant une intégration simple du OAuth2 pour les réseaux sociaux.
Le tout va nous permettre un système de sign-in & login comme on les aime.
Un beau dessin
Alors avant de commencer on va déjà se mettre d’accord sur où on va. Pour ce faire au lieu de faire un gros pavé, on va faire un gros dessin.

Il est pas beau mon schéma ? Ho oui c’est de toute beauté! Bon OK, ça ne m’a pas pris un temps fou mais c’est quand même bien sympa !
Alors du coup c’est assez simple, que l’utilisateur veuille passer par les réseaux sociaux ou via le mail classique on le renverra vers un formulaire intermédiaire. À la différence que via les réseaux sociaux il passera par l’API correspondante et on aura déjà les infos !
Installation
Commençons par installer nos chers bundles
composer.son
"require": {
...
"friendsofsymfony/user-bundle": "~1.3",
"hwi/oauth-bundle": "0.4.*@dev",
...
},Et on met à jour
php composer.phar update
Je ne reviens pas sur l’installation de FOS user, tout est très bien expliqué sur la page officielle !
Move fast and break things
Ho oui comme disait Mark Zuckerberg a une certaine époque, on passe aux choses sérieuses, il y a de nombreux fichiers à configurer pour arriver à nos fins.
Alors dans l’ordre :
Acme/UserBundle/Entity/User.php
<?php
namespace Acme\UserBundle\Entity;
use FOS\UserBundle\Entity\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* @ORM\Entity
* @ORM\Table(name="fos_user")
*/
class User extends BaseUser
{
public function __construct()
{
parent::__construct();
$this->media = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @var string
*
* @ORM\Column(name="facebook_id", type="string", nullable=true)
*/
protected $facebook_id;
/**
* @var string
*
* @ORM\Column(name="google_id", type="string", nullable=true)
*/
protected $google_id;
/**
* @var string
*
* @ORM\Column(name="twitter_id", type="string", nullable=true)
*/
protected $twitter_id;
}Ici on ajuste notre entité utilisateur à nos besoins. Je rajoute l’id unique et indispensable pour connexion de chaque réseau social. On les met bien évidement en non obligatoire pour nos amis hater des réseaux sociaux pour qu’ils puissent se connecter sans ces derniers. Hé oui on aime tout le monde !

N’oubliez pas de faire un petit : php app/console doctrine:generate:entities AcmeUserBundle pour créer les getters et setters.
App/config/config.yml
fos_user:
db_driver: orm
firewall_name: main
user_class: Acme\UserBundle\Entity\User
registration:
form:
type: acme_user_registration
confirmation:
enabled: false
hwi_oauth:
connect:
confirmation: true
registration_form: fos_user.registration.form
firewall_name: main
fosub:
username_iterations: 30
properties:
facebook: facebook_id
google: google_id
twitter: twitter_id
resource_owners:
# Facebook Access
facebook:
type: facebook
client_id: [ClientID]
client_secret: [ClientIDSecret]
scope: "email"
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: ""Le fichier de config !
Ici je me contente de Facebook, Google et Twitter.
Pour accéder aux infos de chaque réseau social, il faudra créer une application concernant votre site sur le réseau social en question. Cette application vous permettra de faire communiquer votre site avec les données de l’utilisateur sur le réseau social. Concernant les deux id : ‘client_id’ et ‘client_secret’ il s’agit des deux clefs d’authentification de votre application.
Voici les liens par réseaux pour créer une application et obtenir les deux clefs d’API nécessaire à la connexion.
Une fois sur ces liens suivez les explications vous allez pouvoir créer vos applications en peu de temps.
App/config/routing.yml
# HWIOAuthBundle
hwi_oauth_security:
resource: "@HWIOAuthBundle/Resources/config/routing/login.xml"
prefix: /connect
hwi_oauth_redirect:
resource: "@HWIOAuthBundle/Resources/config/routing/redirect.xml"
prefix: /connect
# We override this part of the routing
hwi_oauth_connect:
resource: "@AcmeAcmeBundle/Resources/config/routing/connect.xml"
prefix: /connect
facebook_login:
pattern: /connect/check-facebook
google_login:
pattern: /connect/check-google
twitter_login:
pattern: /connect/check-twitter
# FOSUser
fos_user_security:
resource: "@FOSUserBundle/Resources/config/routing/security.xml"
fos_user_register:
resource: "@FOSUserBundle/Resources/config/routing/registration.xml"
prefix: /register
fos_user_resetting:
resource: "@FOSUserBundle/Resources/config/routing/resetting.xml"
prefix: /resetting
fos_user_change_password:
resource: "@FOSUserBundle/Resources/config/routing/change_password.xml"
prefix: /profileLe fichier de routing. On rajoute nos routes pour chaque réseau social pour différencier l’appel vers les apis correspondantes, mais surtout on surcharge une partie du routing par notre fichier qui traitera, l’inscription par les réseaux sociaux.
AcmeAcmeBundle/Resources/config/routing/connect.xml
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd">
<route id="hwi_oauth_connect_service" pattern="/service/{service}">
<default key="_controller">HWIOAuthBundle:Connect:connectService</default>
</route>
<route id="hwi_oauth_connect_registration" pattern="/registration/{key}">
<default key="_controller">AcmeAcmeBundle:Connect:registration</default>
</route>
</routes>Le fichier de route qui nous permettra de faire ce qu’on veut, on aime ça faire ce qu’on veut, lors de l’inscription d’une personne sur notre appli. Je détaillerai le contenu du controller registration plus bas. Mais avant, ENCORE de la config… C’est bientôt fini !

app/config/security.yml
security:
encoders:
FOS\UserBundle\Model\UserInterface: sha512
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
fos_userbundle:
id: fos_user.user_provider.username_email
firewalls:
main:
pattern: ^/
form_login:
provider: fos_userbundle
csrf_provider: form.csrf_provider
login_path: /login
check_path: /login_check
oauth:
resource_owners:
facebook: "/connect/check-facebook"
google: "/connect/check-google"
twitter: "/connect/check-twitter"
login_path: /connect
failure_path: /connect
oauth_user_provider:
service: hwi_oauth.user.provider.fosub_bridge
logout: true
anonymous: true
login:
pattern: ^/login$
security: false
remember_me:
key: "%secret%"
lifetime: 31536000
path: /
domain: ~
access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }Le fichier de sécurité indispensable aux connexions.
Ok Google
Overdose de config je suis d’accord mais pas le choix si on veut faire tourner la machine nickel ! Et c’est pas la première, ni la dernière fois que vous configurez un truc hein ! Bref on passe au contrôleur.
Alors on surcharge le contrôleur qui se trouve la HWIOAuthBundle:Connect, on va le chercher et on en fait une copie dans notre bundle à AcmeAcmeBundle:Connect.
Vous l’aurez remarqué on ne surcharge que la méthode registration, cette méthode nous permet d’avoir un contrôle total lors de l’inscription via réseau social, avant et après soumission du formulaire:
src/Acme/AcmeBundle/Controller/ConnectController.php
<?php
namespace Acme\AcmeBundle\Controller;
use HWI\Bundle\OAuthBundle\OAuth\ResourceOwnerInterface;
use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
use HWI\Bundle\OAuthBundle\Security\Core\Exception\AccountNotLinkedException;
use Symfony\Component\DependencyInjection\ContainerAware;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Exception\AccountStatusException;
use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\SecurityEvents;
class ConnectController extends ContainerAware
{
public function registrationAction(Request $request, $key)
{
$connect = $this->container->getParameter('hwi_oauth.connect');
if (!$connect) {
throw new NotFoundHttpException();
}
$hasUser = $this->container->get('security.context')->isGranted('IS_AUTHENTICATED_REMEMBERED');
if ($hasUser) {
throw new AccessDeniedException('Cannot connect already registered account.');
}
$session = $request->getSession();
$error = $session->get('_hwi_oauth.registration_error.'.$key);
$session->remove('_hwi_oauth.registration_error.'.$key);
if (!($error instanceof AccountNotLinkedException) || (time() - $key > 300)) {
throw new \Exception('Cannot register an account.');
}
$userInformation = $this
->getResourceOwnerByName($error->getResourceOwnerName())
->getUserInformation($error->getRawToken())
;
// enable compatibility with FOSUserBundle 1.3.x and 2.x
if (interface_exists('FOS\UserBundle\Form\Factory\FactoryInterface')) {
$form = $this->container->get('hwi_oauth.registration.form.factory')->createForm();
} else {
$form = $this->container->get('hwi_oauth.registration.form');
}
$formHandler = $this->container->get('hwi_oauth.registration.form.handler');
if ($formHandler->process($request, $form, $userInformation)) {
// Connect user
$this->container->get('hwi_oauth.account.connector')->connect($form->getData(), $userInformation);
// Authenticate the user
$this->authenticateUser($request, $form->getData(), $error->getResourceOwnerName(), $error->getRawToken());
// Getting user
$user = $this->container->get('security.context')->getToken()->getUser();
// Getting social network source
$source = $userInformation->getResourceOwner()->getName();
// Updating user by source
switch ($source) {
case 'facebook':
$user = $this->handleFacebookResponse($userInformation, $user);
break;
case 'google':
$user = $this->handleGoogleResponse($userInformation, $user);
break;
case 'twitter':
$user = $this->handleTwitterResponse($userInformation, $user);
break;
}
// Saving User
$em = $this->container->get('doctrine.orm.entity_manager');
$em->persist($user);
$em->flush();
// Redirect the user to homepage
$url = $this->container->get('router')->generate(
'acme_acme_homepage'
);
return new RedirectResponse($url);
}
// reset the error in the session
$key = time();
$session->set('_hwi_oauth.registration_error.'.$key, $error);
return $this->container->get('templating')->renderResponse('HWIOAuthBundle:Connect:registration.html.' . $this->getTemplatingEngine(), array(
'key' => $key,
'form' => $form->createView(),
'userInformation' => $userInformation,
));
}
public function handleFacebookResponse($response, $user) {
// User is from Facebook : DO STUFF HERE \o/
// All data from Facebook
$data = $response->getResponse();
// His profile image : file_get_contents('https://graph.facebook.com/' . $response->getUsername() . '/picture?type=large')
return $user;
}
public function handleGoogleResponse($response, $user) {
// User is from Google: DO STUFF HERE \o/
// All data from Google
$data = $response->getResponse();
// His profile image : file_get_contents($data['picture'])
return $user;
}
public function handleTwitterResponse($response, $user) {
// User is from Twitter: DO STUFF HERE \o/
// All data from Twitter
$data = $response->getResponse();
// His profile image : file_get_contents($data['profile_image_url'])
return $user;
}
}Ici je ne fais que changer la fin du traitement d’inscription, en différenciant les sources de réseaux sociaux pour personnaliser le traitement des informations pour chaque cas.
A noter que si vous voulez changer le formulaire d’inscription, il vous suffira de changer le formulaire d’inscription de FOSUser, il en va de même pour les templates !

Épilogue
Je me contente ici de trois réseaux sociaux, mais si vous en voulez d’autre HWIOAuthBundle en proprose un gros paquet ici.
À vous ensuite d’adapter le code pour chaque réseau social dans les fichiers de configs et dans le contrôleur !
Happy coding !
clearfix
