Dans un précédent article, « Intégrer facilement Facebook Connect et Twitter Connect avec FosUserBundle pour Symfony 2.1″, je vous parlais de l’intégration d’un Facebook Connect et d’un Twitter Connect. Le dit-billet étant un peu passé d’actualité désormais, je vais vous présentez une autre solution. Cette solution s’appuie sur un bundle plus générique et ne se limitant pas qu’à Facebook/Twitter.
Ce tutoriel est pour Symfony2 version >= 2.3, et utilise les bundles « FOSUserBundle » et « HWIOAuthBundle ».
FOSUserBundle
Pour ce passage, référez vous à l’article cité plus haut. Cela vous expliquera comment récupérer et configurer le bundle FOSUser.
Si vous êtes en train de lire cet article, c’est que vous voulez implémenter d’autres authentification et inscription que la « basique » user / password. Si vous doutez encore de l’utilité, je suis tombé dernièrement sur une infographie assez sympa. D’ailleurs, je ne pensais pas que le Google Login était autant utilisé. Cependant, ça tombe bien, le bundle que je vais vous présenter et vous aider à configurer réponds tout à fait à ce besoin aussi !
HWIOAuthBundle
Adresse du repo du bundle : https://github.com/hwi/HWIOAuthBundle
Les logins supportées : 37signals, Amazon, Bitbucket, Bitly, Box, Dailymotion, DeviantArt, Disqus, Dropbox, Facebook, Flickr, Foursquare, GitHub, Google, Instagram, JIRA, LinkedIn, Mail.ru Odnoklassniki, QQ, Salesforce, Sensio Connect, Sina, Weibo, Stack Exchange, Stereomood, Twitch, Twitter, VKontakte, Windows Live, WordPress, Yahoo, Yandex.
Autant vous dire, que vous avez l’embarras du choix. Bien entendu, je ne vous conseille pas de tous les mettre ! Choisissez ceux adaptés à votre cible et essayez de vous limiter.
Bref, commençons. Tout d’abord, comme d’hab, il faut ajouter dans votre composer.json / require :
{ "require": { "hwi/oauth-bundle": "0.4.*@dev" } }
Une fois ceci fait, ajouter dans votre app/AppKernel.php :
public function registerBundles() { $bundles = array( // ... new HWI\Bundle\OAuthBundle\HWIOAuthBundle(), ); }
Jusqu’ici rien de compliqué, on est dans la démarche habituelle d’installation d’un nouveau bundle.
Une fois ceci fait, ajoutez la propriété facebookId à votre entité User. Puis générez les getters/setters via la console.
protected $facebookId;
php app/console doctrine:generate:entities Acme/UserBundle/Entity/User
Nous allons nous attaquer à la configuration du security.yml. Rien de bien compliqué à nouveau.
Tout d’abord, dans votre parameters.yml ajoutez l’id de votre application Facebook ainsi que la clé secrète.
oauth.facebook.id = "id_app" oauth.facebook.secret = "secret_key_app"
Puis mettez à jour votre fichier de config, en ajoutant ceci :
hwi_oauth: firewall_name: main resource_owners: facebook: type: facebook client_id: %oauth.facebook.id% client_secret: %oauth.facebook.secret%
Vous voila avec une configuration propre. Vous pouvez désormais configurer votre fichier security.yml de la sorte :
security: firewalls: main: context: user pattern: /.* oauth: resource_owners: facebook: "/login/check-facebook" login_path: /connect failure_path: /connect oauth_user_provider: service: rkueny.oauth.user_provider logout: true anonymous: true
Pour le fichier complet, n’oubliez pas de vous référer au premier billet sur l’installation du bundle FOSUserBundle.
Vous ne devriez pas avoir rencontré de problèmes jusqu’ici. Avant de créer le service rkueny.oauth.user_provider nous allons configurer (et oui encore de la configuration !), le fichier de routing.
Il vous suffit d’importer les routes du plugin et d’ajouter la route Facebook configurée. Pour éviter certains bugs (d’après la doc officielle), l’import des routes du plugin doit être avant les votres. Ainsi nous avons dans le fichier de routing :
hwi_oauth_redirect: resource: "@HWIOAuthBundle/Resources/config/routing/redirect.xml" prefix: /connect facebook_login: pattern: /login/check-facebook
Créons à présent le service rkueny.oauth.user_provider. Pour cela créez le fichier src/RKueny/UserBundle/UserProvider.php et mettez y ce qui suit :
<?php namespace RKUENY\UserBundle\OAuth; use FOS\UserBundle\Model\UserInterface as FOSUserInterface; use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface; use HWI\Bundle\OAuthBundle\Security\Core\User\FOSUBUserProvider; use RKUENY\UserBundle\Entity\User; use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; use Symfony\Component\Security\Core\User\UserInterface; /** * Loading and ad-hoc creation of a user by an OAuth sign-in provider account. * * @author Fabian Kiss <fabian.kiss@ymc.ch> */ class UserProvider extends FOSUBUserProvider { /** * {@inheritDoc} */ public function loadUserByOAuthUserResponse(UserResponseInterface $response) { try { return parent::loadUserByOAuthUserResponse($response); } catch (UsernameNotFoundException $e) { if (null === $user = $this->userManager->findUserByEmail($response->getEmail())) { return $this->createUserByOAuthUserResponse($response); } return $this->updateUserByOAuthUserResponse($user, $response); } } /** * {@inheritDoc} */ public function connect(UserInterface $user, UserResponseInterface $response) { $providerName = $response->getResourceOwner()->getName(); $uniqueId = $response->getUsername(); $user->addOAuthAccount($providerName, $uniqueId); $this->userManager->updateUser($user); } /** * Ad-hoc creation of user * * @param UserResponseInterface $response * * @return User */ protected function createUserByOAuthUserResponse(UserResponseInterface $response) { $user = $this->userManager->createUser(); $this->updateUserByOAuthUserResponse($user, $response); // set default values taken from OAuth sign-in provider account if (null !== $email = $response->getEmail()) { $user->setEmail($email); } if (null === $this->userManager->findUserByUsername($response->getNickname())) { $user->setUsername($response->getNickname()); } $user->setEnabled(true); return $user; } /** * Attach OAuth sign-in provider account to existing user * * @param FOSUserInterface $user * @param UserResponseInterface $response * * @return FOSUserInterface */ protected function updateUserByOAuthUserResponse(FOSUserInterface $user, UserResponseInterface $response) { $providerName = $response->getResourceOwner()->getName(); $providerNameSetter = 'set'.ucfirst($providerName).'Id'; $user->$providerNameSetter($response->getUsername()); if(!$user->getPassword()) { // generate unique token $secret = md5(uniqid(rand(), true)); $user->setPassword($secret); } return $user; } }
Je vous conseille de vous attarder un peu sur ce fichier pour bien le comprendre et le maitriser. La seule chose « bizarre » que j’ai réalisée est dans la méthode « updateUserByOAuthUserResponse ». C’est lorsqu’un utilisateur utilise le Facebook Connect je lui génère un mot de passe. Il ne l’utilise en fait pas par la suite. Si vous voulez faire un Facebook Connect qui ne remplit que les infos il ne faudra bien sûr pas faire comme cela.
Après la création de ce fichier, il vous faut le déclarer comme service. Ce n’est pas compliqué à faire, il y a plusieurs façons voici la mienne, dans le fichier src/RKueny/UserBundle/Resources/config/services.xml :
<?xml version="1.0" encoding="UTF-8"?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> <parameters> <parameter key="rkueny.oauth.user_provider.class">RKueny\UserBundle\OAuth\UserProvider</parameter> </parameters> <services> <service id="rkueny.oauth.user_provider" class="%rkueny.oauth.user_provider.class%"> <argument type="service" id="fos_user.user_manager" /> <argument type="collection"> <argument key="facebook">facebookId</argument> </argument> </service> </services> </container>
Nous avons fini ici la configuration du plugin. Vous pouvez ajouter les logins à votre site via le render du plugin :
{{ render(url('hwi_oauth_connect')) }}
Vous avez désormais un site acceptant les logins de différents services