Tutoriel Vidéo Symfony Authentification social sur Symfony

Télécharger la vidéo Télécharger les sources

Bienvenue dans cette vidéo où je vous propose de découvrir coment mettre en place une authentificiation via les réseaux sociaux en utilisant le bundle KnpUOAuth2ClientBundle.

00:00 Introduction et configuration de l'application symfony
08:40 Utilisation de KnpUOAuth2ClientBundle
36:08 Récupérer l'email dans le cas de github

Les étapes de mises en place

Dans un premier temps il va falloir installer le bundle ainsi que le client que l'on souhaite mettre en place.

composer require knpuniversity/oauth2-client-bundle
composer require league/oauth2-github

Ensuite on va créer une application OAuth sur le réseau social qui nous intérèsse et récupérer l'id et la clef secrète que l'on mettre dans nos variable d'environnement.

GITHUB_ID=c39f620296adf6722a5c
GITHUB_SECRET=37eaa24ec6d843e52fa80bbc2ba9055e475db8e4

On configure notre client en modifiant le fichier config/packages/knpu_oauth2_client.yaml

knpu_oauth2_client:
    clients:
        github:
            type: github
            client_id: '%env(GITHUB_ID)%'
            client_secret: '%env(GITHUB_SECRET)%'
            redirect_route: oauth_check
            redirect_params:
                service: github

Ce client pourra ensuite être récupéré au travers du service KnpU\OAuth2ClientBundle\Client\ClientRegistry. On l'utilisera d'ailleurs pour rediriger l'utilisateur vers le portail d'authentification.

/**
 * @Route("/connect/github", name="github_connect")
 */
public function connect(ClientRegistry $clientRegistry): RedirectResponse
{
    /** @var GithubClient $client */
    $client = $clientRegistry->getClient('github');
    return $client->redirect(['read:user', 'user:email']);
}

Pour gérer le retour, on utilisera une route qui sera intercepté par notre authenticator.

# config/routes.yaml
oauth_check:
  path: /oauth/check/{service}
  controller: Symfony\Bundle\FrameworkBundle\Controller\TemplateController
``

Enfin, il ne nous reste plus qu'à créer l'authenticator qui va intercepter la requête et authentifier l'utilisateur.

```php
<?php

namespace App\Security;

use App\Repository\UserRepository;
use App\Security\Exception\NotVerifiedEmailException;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use KnpU\OAuth2ClientBundle\Client\Provider\GithubClient;
use KnpU\OAuth2ClientBundle\Security\Authenticator\SocialAuthenticator;
use League\OAuth2\Client\Provider\GithubResourceOwner;
use League\OAuth2\Client\Token\AccessToken;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
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\Security;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Util\TargetPathTrait;

class GithubAuthenticator extends SocialAuthenticator
{

    use TargetPathTrait;

    private RouterInterface $router;
    private ClientRegistry $clientRegistry;
    private UserRepository $userRepository;

    public function __construct (RouterInterface $router, ClientRegistry $clientRegistry, UserRepository $userRepository) {

        $this->router = $router;
        $this->clientRegistry = $clientRegistry;
        $this->userRepository = $userRepository;
    }

    public function start(Request $request, AuthenticationException $authException = null)
    {
        return new RedirectResponse($this->router->generate('app_login'));
    }

    /**
     * Si la route correspond à celle attendue, alors on déclenche cet authenticator
    **/
    public function supports(Request $request)
    {
        return 'oauth_check' === $request->attributes->get('_route') && $request->get('service') === 'github';
    }

    public function getCredentials(Request $request)
    {
        return $this->fetchAccessToken($this->getClient());
    }

    /**
     * Récupère l'utilisateur à partir du AccessToken
     * 
     * @param AccessToken $credentials
     */
    public function getUser($credentials, UserProviderInterface $userProvider)
    {
        /** @var GithubResourceOwner $githubUser */
        $githubUser = $this->getClient()->fetchUserFromToken($credentials);

        // On récupère l'email de l'utilisateur (spécifique à github)
        $response = HttpClient::create()->request(
            'GET',
            'https://api.github.com/user/emails',
            [
                'headers' => [
                    'authorization' => "token {$credentials->getToken()}"
                ]
            ]
        );
        $emails = json_decode($response->getContent(), true);
        foreach($emails as $email) {
            if ($email['primary'] === true && $email['verified'] === true) {
                $data = $githubUser->toArray();
                $data['email'] = $email['email'];
                $githubUser = new GithubResourceOwner($data);
            }
        }

        if ($githubUser->getEmail() === null) {
            throw new NotVerifiedEmailException();
        }

        return $this->userRepository->findOrCreateFromGithubOauth($githubUser);
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
    {
        if ($request->hasSession()) {
            $request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception);
        }

        return new RedirectResponse($this->router->generate('app_login'));
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey)
    {
        $targetPath = $this->getTargetPath($request->getSession(), $providerKey);
        return new RedirectResponse($targetPath ?: '/');
    }

    private function getClient (): GithubClient {
        return $this->clientRegistry->getClient('github');
    }
}

Et voila ! Il ne vous reste plus qu'à rendre le code plus générique afin de l'adapter aux autre réseaux sociaux ;)