Hugo Soltys
Hugo Soltys

My name is Hugo, I'm 27 and I'm a Symfony developer since 2013. I love to create websites by myself to learn new technologies or increase my skills.

M6 Web
2018-10-16 - Today Symfony developer
Decathlon
2016-01-01 - 2018-10-15 Symfony developer
IT Room
2014-09-01 - 2015-12-31 Symfony developer
Noogaa
2013-09-01 - 2014-08-31 Student symfony developer
Work with me

Easily implement Facebook login with Symfony 4

June 6, 2019 by Hugo - 563 views - 0 Comments


Easily implement Facebook login with Symfony 4

Allowing your users to register and login on your website with Facebook is pretty useful and quite standard nowadays. 

Fortunately, doing this with Symfony is quite simple and we will see how to do it in a few steps.


1. Installing KnpUOAuth2ClientBundle

This bundle allows you to easily integrate multiple OAuth2 server.

Open a new terminal an type the following command.

$ composer require knpuniversity/oauth2-client-bundle

 

2. Installing the Facebook Client library

KnpUOAuth2ClientBundle requires that you configure one client for each OAuth2 server you want to implement.

To install the Facebook client, open your teminal and type this command.

$ composer require league/oauth2-facebook

 

3. Register your app on Facebook

To implement Facebook Login on your website, you must before register it on developers.facebook.com

Once logged, click on the Create a new app ID and fill in the form.

Now your app is created, you should see your App dashboard. In the menu, click on Settings > General

Copy you App ID and your secret key in your .env and .env.dist file with the keys OAUTH_FACEBOOK_ID and OAUTH_FACEBOOK_SECRET like the following.

OAUTH_FACEBOOK_ID=my_app_id
OAUTH_FACEBOOK_SECRET=my_app_secret

 

You can do a lot of things with your Facebook App but this is not the subject so let's go back to Symfony.

4. Configuring the client

Open your config/packages/knpu_oauth2_client.yaml and add the following configuration.

knpu_oauth2_client:
    clients:
        # 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: connect_facebook_check
            redirect_params: {}
            graph_api_version: v3.2

 

5. Creating the controller

Your controller must have two actions, the first one to start the connect process and the second is the URL where Facebook will redirect you once you are logged.

// src/Controller/FacebookController.php

<?php

namespace App\Controller;

use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;

class FacebookController extends AbstractController
{
    /**
     * Link to this controller to start the "connect" process
     * @param ClientRegistry $clientRegistry
     *
     * @Route("/connect/facebook", name="connect_facebook_start")
     *
     * @return \Symfony\Component\HttpFoundation\RedirectResponse
     */
    public function connectAction(ClientRegistry $clientRegistry)
    {
        return $clientRegistry
            ->getClient('facebook')
            ->redirect([
                'public_profile', 'email' // the scopes you want to access
            ])
        ;
    }

    /**
     * After going to Facebook, you're redirected back here
     * because this is the "redirect_route" you configured
     * in config/packages/knpu_oauth2_client.yaml
     *
     * @param Request $request
     * @param ClientRegistry $clientRegistry
     *
     * @Route("/connect/facebook/check", name="connect_facebook_check")
     * @return \Symfony\Component\HttpFoundation\RedirectResponse
     */
    public function connectCheckAction(Request $request, ClientRegistry $clientRegistry)
    {
        return $this->redirectToRoute('your_homepage_route');
    }
}

 

6. Creating your guard authenticator

For now, your code allow yours user to authenticate through your OAuth server and get their user informations and access token.

But now, we want to authenticate them to your application, and to do so we will use a guard authenticator instead of putting all the code in the controller.

In my below example, I am using the FriendsOfSymfony user manager to create a new user, but feel free to use your prefered method.

// src/Security/FacebookAuthenticator.php

<?php

namespace App\Security;

use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use FOS\UserBundle\Model\UserManagerInterface;
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\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserProviderInterface;

class FacebookAuthenticator extends SocialAuthenticator
{
    /**
     * @var ClientRegistry
     */
    private $clientRegistry;

    /**
     * @var EntityManagerInterface
     */
    private $em;

    /**
     * @var UserManagerInterface
     */
    private $userManager;

    /**
     * FacebookAuthenticator constructor.
     * @param ClientRegistry $clientRegistry
     * @param EntityManagerInterface $em
     * @param UserManagerInterface $userManager
     */
    public function __construct(ClientRegistry $clientRegistry, EntityManagerInterface $em, UserManagerInterface $userManager)
    {
        $this->clientRegistry = $clientRegistry;
        $this->em = $em;
        $this->userManager = $userManager;
    }

    /**
     * @param Request $request
     * @return bool
     */
    public function supports(Request $request)
    {
        // continue ONLY if the current ROUTE matches the check ROUTE
        return $request->attributes->get('_route') === 'connect_facebook_check';
    }

    /**
     * @param Request $request
     * @return \League\OAuth2\Client\Token\AccessToken|mixed
     */
    public function getCredentials(Request $request)
    {
        // this method is only called if supports() returns true

        return $this->fetchAccessToken($this->getFacebookClient());
    }

    /**
     * @param mixed $credentials
     * @param UserProviderInterface $userProvider
     * @return User|null|object|\Symfony\Component\Security\Core\User\UserInterface
     */
    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(['facebookId' => $facebookUser->getId()]);

        if ($existingUser) {
            $user = $existingUser;
        } else {
            // 2) do we have a matching user by email?
            $user = $this->em->getRepository(User::class)
                ->findOneBy(['email' => $email]);

            if (!$user) {
                /** @var User $user */
                $user = $this->userManager->createUser();
                $user->setEnabled(true);
                $user->setEmail($email);
                $user->setUsername("your chosen username");
                $user->setPlainPassword("your chosen password");
            }
        }

        // 3) Maybe you just want to "register" them by creating
        // a User object
        $user->setFacebookId($facebookUser->getId());
        $this->userManager->updateUser($user);

        return $userProvider->loadUserByUsername($user->getUsername());
    }

    /**
     * @return FacebookClient
     */
    private function getFacebookClient()
    {
        return $this->clientRegistry->getClient('facebook');
    }

    /**
     * @param Request $request
     * @param TokenInterface $token
     * @param string $providerKey
     * @return null|Response
     */
    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    {
        // on success, let the request continue
        return null;
    }

    /**
     * @param Request $request
     * @param AuthenticationException $exception
     * @return null|Response
     */
    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'.
     *
     * @param Request $request
     * @param AuthenticationException|null $authException
     *
     * @return RedirectResponse
     */
    public function start(Request $request, AuthenticationException $authException = null)
    {
        return new RedirectResponse(
            '/connect/', // might be the site, where users choose their oauth provider
            Response::HTTP_TEMPORARY_REDIRECT
        );
    }
}

 

Now register your authenticator in the security.yaml file.

# config/packages/security.yaml
security:
    # ...
    providers:
        fos_userbundle:
            id: fos_user.user_provider.username_email
    firewalls:
    	# ...
        main:
	    # ...
            guard:
                provider: fos_userbundle
                authenticators:
                    - App\Security\FacebookAuthenticator

 

7. Testing your Facebook Login button

In your login template (or anywhere else, whatever) add the following link to use your new Facebook Login feature.

<a href="{{ path('connect_facebook_start') }}">
    Facebook
</a>

 

Hope this article will help you.

Hugo.