Linkedin Oauth2 for Symfony 4 without Bundle
Tags: php, symfony
Published: 2020-08-24 17:01:27

Integrate Oauth2 Linkedin for Symfony (or other framework) is easy.

Install the lib oauth2-linkedin with composer:

```
composer req league/oauth2-linkedin
```
I am going to define the keys provided by linkedin in .env file like this:

```
LINKEDIN_CLIENT_ID=yourId
LINKEDIN_CLIENT_SECRET=yourSecretId
```
Then we modify the config/services.yaml :

``` yaml
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.

# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
    linkedin_client_id: '%env(LINKEDIN_CLIENT_ID)%'
    linkedin_client_secret: '%env(LINKEDIN_CLIENT_SECRET)%'

services:
    # default configuration for services in *this* file
    _defaults:
        autowire: true      # Automatically injects dependencies in your services.
        autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
```

Let's create the controller that will manage the login.

```php
<?php

namespace App\Controller;

use App\Entity\User;
use App\Service\LinkedinService;
use App\Service\UserService;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use League\OAuth2\Client\Provider\LinkedInResourceOwner;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;

/**
 * Class SecurityController
 * @package App\Controller
 */
class SecurityController extends AbstractController
{

/**
     * @Route("/login", name="app_login", methods={"GET", "POST"})
     * @param AuthenticationUtils $authenticationUtils
     * @return Response
     */
    public function login(AuthenticationUtils $authenticationUtils): Response
    {
        if ($this->getUser()) {
            return $this->redirectToRoute('home_page');
        }

        return $this->render(
            'security/login.html.twig',
            [
                'last_username' => $authenticationUtils->getLastUsername(),
                'error' => $authenticationUtils->getLastAuthenticationError()
            ]
        );
    }

    /**
     * @Route("/login/linkedin", name="app_login_linkedin", methods={"GET", "POST"})
     * @param Request $request
     * @param LinkedinService $service
     * @param EntityManagerInterface $em
     * @return Response
     */
    public function loginLinkedIn(Request $request, LinkedinService $service, EntityManagerInterface $em): Response
    {
        if ($this->getUser()) {
            throw new AccessDeniedHttpException();
        }

        $code = $request->query->get('code');
        if ($code === null) {
            return $service->redirectToLinkedin();
        }

        if (!$service->isAuthenticated($request)) {
            $this->addFlash('error', 'Une erreur est survenue');
            return $this->redirectToRoute('home_page');
        }

        try {
            /**
             * @var LinkedInResourceOwner $userLinkedIn
             */
            $userLinkedIn = $service->getUser($code);
            $user = $em->getRepository(User::class)->findOneBy(['email' => $userLinkedIn->getEmail()]);
            if (!$user instanceof User) {
                $user = (new User())
                    ->setEmail($userLinkedIn->getEmail());

                $em->persist($user);
                $em->flush();
            }

            // Generate UsernamePasswordToken to login user
            /*
            $token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
            $this->tokenStorage->setToken($token);
            $this->session->set('_security_main',
serialize($token));
            */

        } catch (IdentityProviderException $e) {
            $this->addFlash('error', 'Une erreur est survenue');
        }
        
        return $this->redirectToRoute('home_page');
    }



}
```
To finish we create the service which manage oauth2-linkedin:

```php

<?php

namespace App\Service;

use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Provider\LinkedIn;
use League\OAuth2\Client\Provider\ResourceOwnerInterface;
use League\OAuth2\Client\Token\AccessToken;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;

/**
 * Class UserService
 * @package App\Service
 */
class LinkedinService

{
    /**
     * @var LinkedIn
     */
    private $provider;
    /**
     * @var SessionInterface
     */
    private $session;


    /**
     * UserService constructor.
     * @param ParameterBagInterface $bag
     * @param RouterInterface $router
     * @param SessionInterface $session
     */
    public function __construct(ParameterBagInterface $bag, RouterInterface $router, SessionInterface $session)
    {
        $this->provider = new LinkedIn([
            'clientId' => $bag->get('linkedin_client_id'),
            'clientSecret' => $bag->get('linkedin_client_secret'),
            'redirectUri' => $router->generate('app_login_linkedin', [], UrlGeneratorInterface::ABSOLUTE_URL),
        ]);
        $this->session = $session;
    }

    public function redirectToLinkedin(): Response
    {
        $url =$this->provider->getAuthorizationUrl();
        $this->session->set('oauth2state', $this->provider->getState());
        return new RedirectResponse($url);
    }

    /**
     * @param Request $request
     * @return bool
     */
    public function isAuthenticated(Request $request): bool
    {
        $state = $request->query->get('state');

        $isAuthenticated = ($state !== null && $state === $this->session->get('oauth2state'));
        if ($isAuthenticated === false) {
            $this->session->remove('oauth2state');
        }

        return $isAuthenticated;
    }

    /**
     * @param string $code
     * @return ResourceOwnerInterface
     * @throws IdentityProviderException
     */
    public function getUser(string $code):ResourceOwnerInterface
    {
        /**
         * @var AccessToken $token
         */
        $token = $this->provider->getAccessToken('authorization_code', [
            'code' => $code
        ]);

        return $this->provider->getResourceOwner($token);
    }

}
```
Simple and easy!