Vamos a repasar rápidamente cómo integrar la autenticación con Facebook en una aplicación Symfony (4.3.4 en el momento de escribir este artículo).
Dependemos de dos componentes que vamos a instalar usando composer de la manera habitual (en consola):
knpuniversity/oauth2-client-bundle
)league/oauth2-facebook
), necesitaremos configurar un cliente para cada servidor OAuth con el que queramos autenticarnos.$ composer require knpuniversity/oauth2-client-bundle league/oauth2-facebook
También debemos haber registrado nuestra aplicación en Facebook (https://developers.facebook.com) y obtener el APP_ID y la clave (SECRET).
Como estamos utilizando las recetas de autoconfiguración de Symfony 4 y Flex no necesitaremos hacer prácticamente nada, salvo incorporar los datos particulares de nuestra aplicación:
Añade al fichero .env
y .env.local
los parámetros (en el segundo fichero con los datos reales obtenidos de Facebook):
OAUTH_FACEBOOK_ID=app_id OAUTH_FACEBOOK_SECRET=app_secret
Y dentro del fichero config/packages/knpu_oauth2_client.yaml
que se habrá creado en la instalación del bundle incluye los datos del cliente:
knpu_oauth2_client: clients: # configure your clients as described here: https://github.com/knpuniversity/oauth2-client-bundle#configuration # the key "facebook" can be anything, it # will create a service: "knpu.oauth2.client.facebook" facebook: type: facebook client_id: '%env(OAUTH_FACEBOOK_ID)%' client_secret: '%env(OAUTH_FACEBOOK_SECRET)%' # the route that you're redirected to after redirect_route: oauth_facebook_check redirect_params: {} graph_api_version: v4.0
Debemos crear dos rutas y sus correspondientes métodos en el controlador de seguridad (o en otro específico para la autenticación en Facebook).
# config/routes/public.yaml # [...] oauth_facebook: path: /connect/facebook controller: App\Controller\SecurityController:facebookConnect oauth_facebook_check: path: /connect/facebook/check controller: App\Controller\SecurityController:facebookConnectionCheck
<?php // src/Controller/SecurityController.php namespace App\Controller; use KnpU\OAuth2ClientBundle\Client\ClientRegistry; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; class SecurityController extends AbstractController { // [...] public function facebookConnect(ClientRegistry $clientRegistry) { return $clientRegistry ->getClient('facebook') ->redirect([ 'public_profile', 'email' // the scopes you want to access ]) ; } public function facebookConnectionCheck(Request $request, ClientRegistry $clientRegistry) { return $this->redirectToRoute('home'); // or any other route } // [...] }
Y ahora el último paso. Una vez realizada la autenticación en el servidor OAuth y recuperada la información del usuario y el token de acceso tenemos que efectivamente autenticar en nuestra aplicación.
El método de conseguir esto varía según el mecanismo interno de autenticación de cada aplicación. Yo lo voy a describir para la autenticación básica que se describe en este artículo, utilizando el Maker de Symfony (https://symfony.com/blog/new-in-makerbundle-1-8-instant-user-login-form-commands).
Básicamente consiste (para no incorporar el código en el controlador) en crear un nuevo Authenticator que podemos basar en el SocialAuthenticator.
<?php // src/Security/FacebookAuthenticator.php namespace App\Security; use App\Entity\User; //User entity use Doctrine\ORM\EntityManagerInterface; use KnpU\OAuth2ClientBundle\Security\Authenticator\SocialAuthenticator; use KnpU\OAuth2ClientBundle\Client\Provider\FacebookClient; use KnpU\OAuth2ClientBundle\Client\ClientRegistry; 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\Core\User\UserProviderInterface; class FacebookAuthenticator extends SocialAuthenticator { private $clientRegistry; private $em; private $router; public function __construct(ClientRegistry $clientRegistry, EntityManagerInterface $em, RouterInterface $router) { $this->clientRegistry = $clientRegistry; $this->em = $em; $this->router = $router; } public function supports(Request $request) { // continue ONLY if the current ROUTE matches the check ROUTE return $request->attributes->get('_route') === 'oauth_facebook_check'; } public function getCredentials(Request $request) { // this method is only called if supports() returns true return $this->fetchAccessToken($this->getFacebookClient()); } public function getUser($credentials, UserProviderInterface $userProvider) { /** @var FacebookUser $facebookUser */ $facebookUser = $this->getFacebookClient()->fetchUserFromToken($credentials); $email = $facebookUser->getEmail(); // 1) have they logged in with Facebook before? Easy! $existingUser = $this->em->getRepository(User::class) ->findOneBy([ 'origin' => User::ORIGIN_FACEBOOK, 'externalId' => $facebookUser->getId() ]); if ($existingUser) { return $existingUser; } $user = new User(); $user->setEmail($facebookUser->getEmail()); $user->setPassword('NO_PASSWORD'); $user->setOrigin(User::ORIGIN_FACEBOOK); $user->setExternalId($facebookUser->getId()); //TODO: Fill profile $this->em->persist($user); $this->em->flush(); return $user; } /** * @return FacebookClient */ private function getFacebookClient() { // "facebook" is the key used in config/packages/knpu_oauth2_client.yaml return $this->clientRegistry->getClient('facebook'); } public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) { return new RedirectResponse($this->router->generate('home')); // or, on success, let the request continue to be handled by the controller //return null; } public function onAuthenticationFailure(Request $request, AuthenticationException $exception) { $message = strtr($exception->getMessageKey(), $exception->getMessageData()); return new Response($message, Response::HTTP_FORBIDDEN); } /** * Called when authentication is needed, but it's not sent. * This redirects to the 'login'. */ public function start(Request $request, AuthenticationException $authException = null) { return new RedirectResponse( $this->router->generate('app_login'), // might be the site, where users choose their oauth provider Response::HTTP_TEMPORARY_REDIRECT ); } }
Y tenemos que decirle en la configuración que lo utilice. Ahora bien, si seguimos teniendo la autenticación por usuario/contraseña existente tendremos dos clases para la misma funcionalidad, múltiples autenticadores y tenemos obligatoriamente que especificar cuál se utilizará por defecto: el entry point.
# config/packages/security.yaml security: # ... firewalls: main: anonymous: true guard: authenticators: - App\Security\LoginFormAuthenticator - App\Security\FacebookAuthenticator entry_point: App\Security\LoginFormAuthenticator
4 años ago ·
Rodri!, interesante entrada, muy completa, lo único que haría falta es la clase `User`, ya que se ve que tiene algunas propiedades que no viene por defecto al crear el usuario con Symfony.
$user = new User();
...
$user->setOrigin(User::ORIGIN_FACEBOOK);
$user->setExternalId($facebookUser->getId());
...
//TODO: Fill profile
saludos!
3 años ago ·
Gracias Diego por el aporte.