• 50 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 29/07/2019

Tutoriel - Authentifier et autoriser les utilisateurs de l'API

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

 Il s'agit désormais de connaître qui cherche à interroger notre API via une authentification, puis d'autoriser ou non l'utilisation de celle-ci.

Généralités sur l'authentification

En ce qui concerne l'authentification, nous allons voir qu'en réalité il n'y a pas grand chose de nouveau par rapport à ce que vous connaissez déjà : en effet, il est tout à fait possible de passer par les méthodes d'authentification que vous connaissez déjà. Néanmoins, il existe des méthodes d'authentification plus utilisées que d'autres.

Parmi elles, on peut retrouver le très populaire OAuth (versions 1 et 2), implémenté par bon nombre de services de gestion d'utilisateurs, dont Facebook, Twitter ou encore Github. Il est également très simple de mettre en place son propre serveur OAuth (l'un des bundles les plus populaires est le HWIOAuthBundle).

Un autre moyen populaire est d'utiliser Json Web Token (JWT) : tout comme OAuth, il s'agit d'interroger un service pour obtenir un token. La manière d'obtenir ce token et ce qu'il contient diffère du protocole OAuth. Pour en savoir plus concernant JWT, je vous invite à lire attentivement la documentation officielle que vous pouvez retrouver ici : jwt.io. Par ailleurs, il est également tout à fait possible de mettre en place un serveur capable d'une gestion JWT (l'un des bundles les plus populaires est le LexikJWTAuthenticationBundle).

 

Dans un cas comme dans l'autre, ce qui change pour vous en tant que développeur est simplement la manière dont vous allez interagir avec l'un de ces services pour obtenir ce token ainsi que les informations utilisateurs. Plutôt que d'apprendre toutes les manières possibles et imaginables pour authentifier un utilisateur, je vous propose plutôt d'en tirer la philosophie globale pour pouvoir parer à toutes les demandes que vos clients pourraient avoir en matière d'authentification.

Qu'est-ce qu'un token  ?

Autrement appelé access token ou encore jeton d'authentification, cette information représente un utilisateur durant un laps de temps déterminé. Plutôt que de faire transiter le nom d'utilisateur ainsi que le mot de passe, c'est une chaîne de caractères délivrée par un serveur en charge de la gestion des utilisateurs (votre application ou une autre application !) qui délivre ces informations selon une configuration donnée.

 

Nous allons prendre un exemple pour comprendre le mécanisme et s'exercer avec un service réel : Github.

 Implémenter une authentification avec Github

 Le tutoriel qui va suivre intègre de nombreuses fonctionnalités. Dans un premier temps, nous allons créer un client (une application Symfony) en charge de communiquer avec notre API développée jusque là pour en récupérer les articles (cela nous changera de notre habituel Postman ^^). Ce client, nous allons l'appeler frontend app durant tout le chapitre. Cette application comportera trois pages :

  1. Une page d'accueil avec un bouton permettant à l'utilisateur d'autoriser l'accès à ses données personnelles auprès de Github ;

  2. Une deuxième page, uniquement accessible si l'utilisateur est authentifié, et affichant le jeton (token) obtenu depuis Github, permettant de contacter notre API en indiquant qui est l'utilisateur ;

  3. Une troisième page, très sommaire, qui se charge de contacter notre API pour en récupérer la liste d'articles.

Du côté de l'API, nous allons effectuer quelques changements également :

  • nous allons implémenter une authentification auprès de Github grâce au token (jeton d'identification obtenu grâce à la frontend app) que nous aurons reçu à chaque interrogation provenant d'un client ;

  • en plus de la liste d'articles, nous allons ajouter les informations de l'utilisateur authentifié dans l'élément  _embeddedde l'objet JSON.

Beaucoup de travail en perspective !

Reprenons ce que nous venons de nous dire, avec un schéma vous présentant le scénario d'un utilisateur de la frontend app cherchant à obtenir la liste des articles présents dans l'API de gestion d'articles :

 

Scénario utilisation - étape 1
Scénario d'utilisation - étape 1

 

Scénario utilisation - étape 2
Scénario d'utilisation - étape 2

 

Scénario utilisation - étape 3
Scénario d'utilisation - étape 3

 

 

Scénra
Scénario d'utilisation - étape 4

 

Scénario utilisation - étape 5
Scénario d'utilisation - étape 5

Bon bon bon, ça fait du boulot à abattre dites donc ! Étant donné que notre objectif est de nous concentrer sur l'authentification, je vous fournis une application de départ pour notre API de gestion d'articles et la frontend app. Grand luxe ! :soleil:

Installation des applications de départ

Installer l'API de gestion d'articles

Commençons par installer l'API qui va nous servir de base pour nos développements : télécharger le code ici

Je vous invite à dézipper l'archive. Puis à vous placer dans le dossier du projet avec votre terminal préféré pour lancer la commande  suivante :

$ composer install

Assurez-vous que les paramètres dans le fichier parameters.yml sont bien corrects pour faire en sorte que l'application puisse se connecter à votre serveur de base de données.

Il faut aussi renseigner le paramètre weather_api_keycorrespondant à la clé d'API OpenWeatherMap (cf le chapitre précédent).

Notre API a besoin d'une base de données pour stocker l'ensemble de nos articles. Assurez-vous que votre serveur de base de données est bien lancé, puis lancez les commandes suivantes :

$ bin/console doctrine:database:create
$ bin/console doctrine:schema:create

Il ne nous reste plus qu'à lancer le serveur intégré de PHP avec la commande Symfony suivante :

$ bin/console server:run 127.0.0.1:8001

Pour accéder à la liste des articles depuis l'API il suffit de forger une requête GET sur l'URL http://127.0.0.1:8001/articles :

Liste d'articles accessible depuis l'API
Liste d'articles accessible depuis l'API

Passons à l'installation de l'application frontend app.

Installer la frontend app

Il nous faut installer la frontend app : télécharger l'application ici.

Je vous invite à dézipper l'archive. Puis à vous placer dans le dossier du projet avec votre terminal préféré pour lancer la commande suivante :

$ composer install

Il nous faut renseigner quelques paramètres :

  • github_client_id  et github_client_secret: ces paramètres sont utilisés pour l'authentification. Ils permettent de faire en sorte que la frontend app puisse communiquer avec Github.

    • Rendez-vous à l'adresse https://github.com/settings/applications/new pour créer une nouvelle application OAuth vous permettant d'obtenir un client ID ainsi qu'un client secret :

      Création de l'application OAuth pour permettre à l'application frontend app de communiquer avec Github
      Création de l'application OAuth pour permettre à l'application frontend app de communiquer avec Github
    • Cliquez sur "Register application" et récupérez les informations client ID ainsi que client secret afin de les renseigner dans le fichier app/config/parameters.yml  (je cache volontairement les client ID et client secret car il s'agit d'informations confidentielles et sensibles qu'il ne faut jamais communiquer.) :

      Application OAuth créée
      Application OAuth créée sur Github
  • my_api_url: il s'agit de l'URL de base sur laquelle l'API de gestion articles est disponible. Si vous avez suivi les étapes précédentes, il s'agit dehttp://127.0.0.1:8001

Cette application ne requiert pas de base de données, il nous suffit donc de lancer le serveur web interne de PHP avec la commande :

$ bin/console server:run 127.0.0.1:8000

Et voilà ! Tout est en place ! Nous pouvons commencer nos développements. :D

Implémentation de l'authentification dans l'API de gestion d'articles

Avant de commencer le développement, voyons schématiquement les interactions que l'API doit avoir avec Github pour authentifier un utilisateur :

Interaction pour obtenir la liste des articles en passant par une authentification via Github
Interaction pour obtenir la liste des articles en passant par une authentification via Github
1. Forger une requête pour interroger l'API de gestion d'articles

Dans un premier temps, il nous faut un access token valide, récupéré auprès de Github, nous permettant d'avoir un identifiant et ainsi être en mesure de récupérer les informations de l'utilisateur. 

Pour récupérer un access token valide, c'est simple via notre application frontend app ! Rendez-vous à l'adresse http://127.0.0.1:8000/admin, et vous devriez avoir un jeton valide comme montré ci-après (dans votre cas, il sera différent) :

Récupération d'un access token valide
Récupération d'un access token valide

Nous sommes maintenant en mesure de forger notre requête avec Postman :

Requête HTTP pour demander la liste d'articles avec l'access token
Requête HTTP pour demander la liste d'articles avec l'access token

Comme vous pouvez le voir, nous passons l'access token d'une certaine manière, c'est dû à la rfc 6750 qui demande que l'access token transite ainsi.

2. Faire appel au mécanisme d'authentification de Symfony

De nombreuses solutions sont possibles, en particulier avec Guard ! Je vous invite à lire la documentation officielle parlant d'authentification via une clé d'API pour vous imprégner du sujet une première fois.

Il y est présenté la théorie et l'implémentation nécessaire en utilisant la méthode Simple Preauthenticator. C'est ce que nous allons appliquer à notre cas d'utilisation.

Nous avons 3 fichiers à modifier/créer :

  1. nous allons modifier le  app/config/security.yml  pour la configuration de sécurité ;

  2. puis créer la classeGithubAuthenticator, (un service) en charge de la création duPreauthenticatedToken, nécessaire à Symfony pour l'authentification ;

  3. et enfin créer la classe  GithubProvider, (un service) en charge de la récupération des informations de l'utilisateur et la création de l'objet  User.

Pas de panique, nous allons y aller étape par étape !

Modifions le fichier app/config/security.yml  pour faire appel au service (pas encore créé) permettant la récupération des informations utilisateur et l'authentification effective :

security:
    providers:
        provider.github_user:
            id: github_user_provider
    firewalls:
        #…

        secured_area:
            pattern: ^/
            stateless: true
            simple_preauth:
                authenticator: github_authenticator
            provider: provider.github_user

Avec cette configuration, on demande à ce qu'une authentification soit effectuée pour toute l'application (toutes les routes commençant par/).

Il est également indiquéstateless: true: il s'agit de dire que l'authentification doit se passer à chaque requête. Rien n'est gardé d'une requête à l'autre. Cette règle provient de la contrainte REST n°2 présentée au premier chapitre de ce cours. Donc pas de session.

Nous déclarons ensuite la manière dont l'authentification va se dérouler :simple_preauth.github_authenticatorest le service en charge de la création de l'objetSymfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken. Cet objet contiendra, entre autres, l'utilisateur authentifié et les credentials (l'access token dans notre cas). Nous verrons dans peu de temps le code qu'il va falloir écrire dans ce service.

Et enfin, le providergithub_user_provider: le métier du provider est de récupérer les informations de l'utilisateur à authentifier (dans notre cas, il va falloir communiquer avec Github) et créer un objetUser(classe implémentant laSymfony\Component\Security\Core\User\UserInterface) avec ces informations.

Créons maintenant la classeGithubAuthenticator. Créez le dossier Security dans le dossier src/AppBundle. Voici le code de la classe :

<?php

namespace AppBundle\Security;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Http\Authentication\SimplePreAuthenticatorInterface;

class GithubAuthenticator implements SimplePreAuthenticatorInterface, AuthenticationFailureHandlerInterface
{
    public function createToken(Request $request, $providerKey)
    {
        $bearer = $request->headers->get('Authorization');
        $accessToken = substr($bearer, 7);

        return new PreAuthenticatedToken(
            'anon.',
            $accessToken,
            $providerKey
        );
    }

    public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
    {
        $accessToken = $token->getCredentials();

        $user = $userProvider->loadUserByUsername($accessToken);

        return new PreAuthenticatedToken(
            $user,
            $accessToken,
            $providerKey,
            ['ROLE_USER']
        );
    }

    public function supportsToken(TokenInterface $token, $providerKey)
    {
        return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey;
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
    {
        return new Response("Authentication Failed :(", 401);
    }
}

Dans la méthodecreateToken, il s'agit de récupérer l'accesstoken reçu dans la requête.

Access token dans la requête reçue par l'API de gestion d'articles
Access token dans la requête reçue par l'API de gestion d'articles

Une fois récupéré, nous l'ajoutons à l'objetPreauthenticatedTokeninstancié en ligne 34. Nous allons avoir besoin de cet access token à la prochaine étape, c'est pour cela que nous le passons en second paramètre du constructeur de la classePreauthenticatedToken. Néanmoins, l'utilisateur n'est pas encore authentifié, c'est à ce moment là que la méthodeauthenticateToken   est appelée.

Dans cette méthode, il s'agit de récupérer l'utilisateur grâce au provider, c'est pourquoi nous y faisons appel à la ligne 45.

Ceci dit, la classe GithubUserProvidern'existe pas encore, il est temps de la créer !

3. Aller chercher auprès de Github les informations de l'utilisateur que l'on souhaite authentifier

Dans le dossier src/AppBundle/Security, créez la classeGithubUserProvider:

<?php

namespace AppBundle\Security;

use AppBundle\Entity\User;
use GuzzleHttp\Client;
use JMS\Serializer\Serializer;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;

class GithubUserProvider implements UserProviderInterface
{
    private $client;

    public function __construct(Client $client, Serializer $serializer)
    {
        $this->client = $client;
        $this->serializer = $serializer;
    }

    public function loadUserByUsername($username)
    {
        $url = 'https://api.github.com/user?access_token='.$username;

        $response = $this->client->get($url);
        $res = $response->getBody()->getContents();
        $userData = $this->serializer->deserialize($res, 'array', 'json');

        if (!$userData) {
            throw new \LogicException('Did not managed to get your user info from Github.');
        }

        return new User($userData['login'], $userData['name'], $userData['email'], $userData['avatar_url'], $userData['html_url']);
    }

    public function refreshUser(UserInterface $user)
    {
        $class = get_class($user);
        if (!$this->supportsClass($class)) {
            throw new UnsupportedUserException();
        }

        return $user;
    }

    public function supportsClass($class)
    {
        return 'AppBundle\Entity\User' === $class;
    }
}

loadUserByUsernameest la méthode qui est appelée dans l'authenticator, nous y avons passé l'access token, il ne reste plus qu'à contacter Github avec cet access token pour récupérer les informations de l'utilisateur lié. Nous utilisons un client HTTP Guzzle pour créer une requête ligne 26.

4. Créer l'utilisateur avec toutes les informations provenant de Github

Il ne nous reste plus qu'à créer un objet user avec toutes ces informations. Il nous faut créer la classeUserdans le dossier src/AppBundle/Entity :

<?php

namespace AppBundle\Entity;

use Symfony\Component\Security\Core\User\UserInterface;

class User implements UserInterface
{
    private $username;
    private $fullname;
    private $email;
    private $avatarUrl;
    private $profileHtmlUrl;

    public function __construct($username, $fullname, $email, $avatarUrl, $profileHtmlUrl)
    {
        $this->username = $username;
        $this->fullname = $fullname;
        $this->email = $email;
        $this->avatarUrl = $avatarUrl;
        $this->profileHtmlUrl = $profileHtmlUrl;
    }

    public function getUsername()
    {
        return $this->username;
    }

    public function getFullname()
    {
        return $this->fullname;
    }

    public function getEmail()
    {
        return $this->email;
    }

    public function getAvatarUrl()
    {
        return $this->avatarUrl;
    }

    public function getProfileHtmlUrl()
    {
        return $this->profileHtmlUrl;
    }

    public function getRoles()
    {
        return ['ROLE_USER'];
    }

    public function getPassword()
    {
    }

    public function getSalt()
    {
    }

    public function eraseCredentials()
    {
    }
}

 

5. Ajouter les informations de l'utilisateur authentifié à la liste d'utilisateurs

 Il suffit de faire appel à ce que nous savons déjà faire : ajouter un élément_embedded grâce à la librairie Hateoas. Ajoutons l'annotation qui va dans la classe  AppBundle\Representations\Articles étant donné qu'il s'agit de l'objet sérialisé lorsqu'un client demande la liste des articles :

<?php

namespace AppBundle\Representation;

use Hateoas\Configuration\Annotation as Hateoas;
// …

/**
 * @Hateoas\Relation(
 *     "authenticated_user",
 *     embedded = @Hateoas\Embedded("expr(service('security.token_storage').getToken().getUser())")
 * )
 */
class Articles
{
    // …
}

Une fois que l'authentification s'est bien passée, l'utilisateur authentifié est accessible depuis le servicesecurity.token_storage c'est pour cela que nous y faisons appel à la ligne 11 du code présenté ci-dessus.

Et voilà, nous en avons fini avec le code à ajouter à notre API de gestion d'articles, il nous faut tester tout cela ! Vous vous souvenez de la requête forgée avec Postman montrée plus haut ? Il suffit de cliquer sur Send !

Ajout des informations de l'utilisateur authentifié
Ajout des informations de l'utilisateur authentifié

Adapter la frontend app pour requêter l'API de gestion d'articles avec un access token

Revenons à l'application frontend app, ouvrez le controller en charge de la récupération d'articles (AppBundle\Controller\DefaultController). Il nous faut simplement ajouter le headerAuthorizationavec l'access token. Voici le code à ajouter :

<?php

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;

class DefaultController extends Controller
{
    // …
    
    /**
     * @Route("/admin/articles", name="get_articles")
     */
    public function getArticlesAction()
    {
        $response = $this->get('csa_guzzle.client.my_api')
            ->get(
                $this->getParameter('my_api_url').'/articles',
                [
                    'headers' => ['Authorization' => 'Bearer '.$this->getUser()->getUsername()]
                ]
        );

        $articles = $this->get('serializer')->deserialize($response->getBody()->getContents(), 'array', 'json');

        return $this->render('default/articles.html.twig', ['articles' => $articles]);
    }

    // …
}

Il ne nous reste plus qu'à tester cela ! Rendez-vous à l'adresse http://127.0.0.1:8000/admin/articles :

Liste des articles avec les informations de l'utilisateur authentifié
Liste des articles avec les informations de l'utilisateur authentifié

Libre à vous de présenter les informations à votre convenance dans le template app/Resources/views/default/articles.html.twig

Autorisation dans le cadre d'une API REST

J'ai une excellente nouvelle ! Tout ce que vous avez appris en terme d'autorisation ne change pas ! Une fois que vous avez un utilisateur authentifié, le monde de l'autorisation est à vos pieds ! 

Je vous laisse revoir le chapitre dédié à l'autorisation avec Symfony pour vous rafraîchir la mémoire. ^^

 

Rendez-vous au prochain chapitre pour découvrir comment documenter facilement son API.

Exemple de certificat de réussite
Exemple de certificat de réussite