Dans un précédent article, j’avais déjà évoqué ce sujet en prenant comme exemple une authentification Google, on va faire de même avec Facebook. (Sachant que le plus compliqué c’est de récupérer les clé API Facebook)
Un des intérêts de se connecter à l’aide du compte Facebook c’est d’éviter à saisir un énième mot de passe.
Comme d’habitude on va partir de 0 et on va commencer par créer le projet :
symfony new sffacebook
Et y ajouter toutes les librairies que l’on va avoir besoin :
composer req twig
composer req --dev symfony/maker-bundle
composer req security
composer req orm
composer req --dev debug
Et en particulier celles qui vont nous permettre d’utiliser Facebook pour s’authentifier :
composer require knpuniversity/oauth2-client-bundle
composer require league/oauth2-facebook
On va faire mettre de côté Symfony pour récupérer nos clés Facebook (ce qui implique que vous ayez un compte Facebook).
On va d’abord se connecter au portail développeur : https://developers.facebook.com, puis dans le menu : “Mes applications” et ensuite “Créer une app”.
Comme type d’application, on sélectionnera “aucun” :
Et enfin saisir le nom de votre appli (qui ne doit pas contenir les mots “Facebook”, “FB”, …), pour ma part ce sera SymfonyAuth.
Ensuite il faut ajouter le produit “Facebook Login” (Le produit de connexion social numéro un au monde…)
Ensuite dans le menu de gauche “Facebook Login”, cliquer sur “Paramètres” :
Saisir l’URL de redirection OAuth valides : (votre nom de domaine puis /connect/facebook/check, on créera cette route plus tard dans Symfony) :
Ensuite “Paramètres -> Général” et récupérer votre identifiant et la clé secrète, que vous pouvez déjà mettre dans votre fichier .env.local :
OAUTH_FACEBOOK_CLIENT_ID=app_id
OAUTH_FACEBOOK_CLIENT_SECRET=app_secret
A partir de là on a déjà fait le plus gros du travail ! On va maintenant revenir sur Symfony et créer l’entity User à l’aide du makerBundle :
bin/console make:entity
On envoie le tout dans la base de données :
bin/console make:migration
bin/console doctrine:migration:migrate
Ensuite c’est un peu de paramétrage, tout d’abord dans le fichier config/packages/knpu_oauth2_client.yaml :
knpu_oauth2_client:
clients:
# will create service: "knpu.oauth2.client.facebook"
# an instance of: KnpU\OAuth2ClientBundle\Client\Provider\FacebookClient
# composer require league/oauth2-facebook
facebook:
# must be "facebook" - it activates that type!
type: facebook
# add and set these environment variables in your .env files
client_id: '%env(OAUTH_FACEBOOK_CLIENT_ID)%'
client_secret: '%env(OAUTH_FACEBOOK_CLIENT_SECRET)%'
# a route name you'll create
redirect_route: connect_facebook_check
redirect_params: {}
graph_api_version: v2.12
# whether to check OAuth2 "state": defaults to true
# use_state: true
On va maintenant créer un controller :
Et y mettre ce code : (rien d’extraordinaire, public_profile et email sont les scopes auxquels on aura accès), ce controller va nous permettre de nous logger/deloguer (route app_login et app_logout), puis récupérer les informations utilisateur ainsi que le token de Facebook :
<?php
namespace App\Controller;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
class FacebookController extends AbstractController
{
#[Route('/login', name: 'app_login')]
public function index(): Response
{
return $this->render('facebook/index.html.twig');
}
#[Route('/logout', name: 'app_logout')]
public function logout()
{
throw new \Exception('Don\'t forget to activate logout in security.yaml');
}
#[Route('/connect/facebook', name: 'connect_facebook')]
public function connectAction(ClientRegistry $clientRegistry): RedirectResponse
{
//Redirect to facebook
return $clientRegistry->getClient('facebook')->redirect(['public_profile', 'email'], []);
}
/**
* After going to facebook, you're redirected back here
* because this is the "redirect_route" you configured
* in config/packages/knpu_oauth2_client.yaml
*/
#[Route('/connect/facebook/check', name: 'connect_facebook_check')]
public function connectCheckAction(Request $request)
{
// ** if you want to *authenticate* the user, then
// leave this method blank and create a Guard authenticator
}
}
Et dans le twig (facebook/index.html.twig), un joli bouton pour se connecter via Facebook :
{% extends 'base.html.twig' %}
{% block body %}
<a href="{{ path('connect_facebook') }}">
<img class="img" src="/facebooklogo.png" alt="">
</a>
{% endblock %}
On va maintenant créer le GuardAuthenticator qui va nous permettre de nous connecter à notre application , c’est un poil plus compliqué :
<?php
# src/Security/FacebookAuthenticator.php
namespace App\Security;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use KnpU\OAuth2ClientBundle\Security\Authenticator\OAuth2Authenticator;
use League\OAuth2\Client\Provider\FacebookUser;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
class FacebookAuthenticator extends OAuth2Authenticator
{
public function __construct(
readonly ClientRegistry $clientRegistry,
readonly EntityManagerInterface $entityManager,
readonly RouterInterface $router)
{
}
public function supports(Request $request): ?bool
{
// continue ONLY if the current ROUTE matches the check ROUTE
return $request->attributes->get('_route') === 'connect_facebook_check';
}
public function authenticate(Request $request): Passport
{
$client = $this->clientRegistry->getClient('facebook');
$accessToken = $this->fetchAccessToken($client);
return new SelfValidatingPassport(
new UserBadge($accessToken->getToken(), function () use ($accessToken, $client) {
/** @var FacebookUser $facebookUser */
$facebookUser = $client->fetchUserFromToken($accessToken);
$email = $facebookUser->getEmail();
// have they logged in with Facebook before? Easy!
$existingUser = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $email]);
//User doesnt exist, we create it !
if (!$existingUser) {
$existingUser = new User();
$existingUser->setEmail($email);
$this->entityManager->persist($existingUser);
}
$this->entityManager->flush();
return $existingUser;
})
);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
// change "app_dashboard" to some route in your app
return new RedirectResponse(
$this->router->generate('app_default')
);
// or, on success, let the request continue to be handled by the controller
//return null;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
{
$message = strtr($exception->getMessageKey(), $exception->getMessageData());
return new Response($message, Response::HTTP_FORBIDDEN);
}
Et enfin last but not least, il faut que l’on dise à Symfony d’utiliser ce guard, et ça ça se fait dans le fichier config/packages/security.yaml :
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: app_user_provider
form_login:
login_path: app_login
check_path: app_login
logout:
path: /logout
target: /login
custom_authenticators:
- App\Security\FacebookAuthenticator
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
- { path: ^/login, roles: PUBLIC_ACCESS }
- { path: ^/connect, roles: PUBLIC_ACCESS }
- { path: ^/, roles: ROLE_USER }
Vous pouvez maintenant vous connecter à votre site, vous serez redirigé vers la page /login, et vous n’aurez plus à cliquer sur le bouton et hop :
Derniere astuce, pour vous deconnecter il suffit de se rendre sur /logout.
Vous pourrez retrouver toutes les sources sur le github : https://github.com/gponty/sffacebook