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.

iGraal
2019-11-21 - Today Freelance Symfony developer
M6 Web
2018-10-16 - 2019-11-20 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 Google login with Symfony 4

June 9, 2019 by Hugo - 3032 views - 0 Comments


Easily implement Google login with Symfony 4

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

In this article, we will see how to do it easily with Symfony 4.


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 Google Client library

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

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

$ composer require league/oauth2-google

 

3. Get your Google credentials

To use this package you will need a client id and client secret provided by Google. 

To create your credentials you can go to the Google Credentials page and click on the Create credentials > OAuth client ID.

Then, copy them in your .env and .env.dist files with the keys OAUTH_GOOGLE_ID and OAUTH_GOOGLE_SECRET like the following.

OAUTH_GOOGLE_ID=my_id
OAUTH_GOOGLE_SECRET=my_secret

 

4. Configuring the client

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

knpu_oauth2_client:
    clients:
        # the key "google" can be anything, it
        # will create a service: "knpu.oauth2.client.google"
        google:
            type: google
            client_id: '%env(OAUTH_GOOGLE_ID)%'
            client_secret: '%env(OAUTH_GOOGLE_SECRET)%'
            # the route that you're redirected to after
            redirect_route: connect_google_check
            redirect_params: {}

 

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 Google will redirect you once you are logged.

// src/Controller/GoogleController.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 GoogleController extends AbstractController
{
    /**
     * Link to this controller to start the "connect" process
     * @param ClientRegistry $clientRegistry
     *
     * @Route("/connect/google", name="connect_google_start")
     *
     * @return \Symfony\Component\HttpFoundation\RedirectResponse
     */
    public function connectAction(ClientRegistry $clientRegistry)
    {
        return $clientRegistry
            ->getClient('google')
            ->redirect([
                'profile', 'email' // the scopes you want to access
            ])
        ;
    }

    /**
     * After going to Google, 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/google/check", name="connect_google_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/GoogleAuthenticator.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\GoogleClient;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use League\OAuth2\Client\Provider\GoogleUser;
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 GoogleAuthenticator extends SocialAuthenticator
{
    /**
     * @var ClientRegistry
     */
    private $clientRegistry;

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

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

    /**
     * GoogleAuthenticator 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_google_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->getGoogleClient());
    }

    /**
     * @param mixed $credentials
     * @param UserProviderInterface $userProvider
     * @return User|null|object|\Symfony\Component\Security\Core\User\UserInterface
     */
    public function getUser($credentials, UserProviderInterface $userProvider)
    {
        /** @var GoogleUser $googleUser */
        $googleUser = $this->getGoogleClient()
            ->fetchUserFromToken($credentials);

        $email = $googleUser->getEmail();

        // 1) have they logged in with Google before? Easy!
        $existingUser = $this->em->getRepository(User::class)
            ->findOneBy(['googleId' => $googleUser->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->setGoogleId($googleUser->getId());
        $this->userManager->updateUser($user);

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

    /**
     * @return GoogleClient
     */
    private function getGoogleClient()
    {
        return $this->clientRegistry->getClient('google');
    }

    /**
     * @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\GoogleAuthenticator

 

7. Testing your Google Login button

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

<a href="{{ path('connect_google_start') }}">
    Google
</a>

 

Hope this article will help you.

Hugo.