TD4: Authentification

Pour l'instant, notre application n'a pas d'identification. On souhaite maintenant ajouter des utilisateurs, leur permettre de s'inscrire à travers l'API, puis de se connecter.

Certaines requêtes de l'API nécessiteront un compte par la suite.

Utilisateurs et inscription

Ajout des utilisateurs

Tout d'abord, ajoutez une entité utilisateur :

symfony console make:user

Mettez en suite à jour le schéma de la base de données :

symfony console doctrine:schema:update --force

Créez ensuite un formulaire d'inscription, il nous servira d'exemple pour créer l'équivalent en version "API" (Attention: répondez "no" à la question vous demandant d'envoyer un e-mail):

symfony console make:registration

Inscription via l'API

Il est désormais possible de s'inscrire via la route /register à l'aide d'une adresse e-mail et d'un mot de passe. Ajoutez un endpoint dans l'API :

  • Adresse: /api/register
  • Verbe: POST
  • Contenu de la requête: les données d'un utilisateur en JSON

Vous pourrez fonctionner de la même manière que POST /api/message. Inspirez vous du contenu du RegistrationController, notamment pour hasher le mot de passe.

Les erreurs (par exemple, si l'e-mail n'est pas renseignée ou déjà utilisée) doivent remonter à travers l'API également.

Note: À l'aide des tags, vous pouvez commencer à regrouper les mots d'API par section comme cela:

Authentification

Ajout des jetons d'identification dans la base

Nous allons authentifier nos utilisateurs à l'aide d'un jeton. Pour cela, utilisez :

symfony console make:entity

Pour ajouter un champ token dans l'entité User de type string nullable.

N'oubliez pas de répercuter le changement dans la base à l'aide de :

symfony console doctrine:schema:update --force

Modifier votre inscription afin que le jeton soit généré aléatoirement et stocké dans la base de données.

Distribution du jeton

À l'aide de la documentation officielle, créez maintenant un endpoint /api/login, qui permettra de distribuer le jeton.

Voici à quoi pourrait ressembler le endpoint

#[View()]
#[Route('/login', methods: ['POST'], name: "api_login")]
#[OA\Post(summary: "Login",tags: ["User"])]
#[OA\RequestBody(content: 
    new OA\JsonContent(
        example: '{"username": "user@user.com", "password": "password"}'
))]
public function login(#[CurrentUser()] User $user)
{
    return $user->getToken();
}

Testez votre endpoint de connexion à l'aide d'un compte utilisateur inscrit.

Création de l'authentification basée jeton

Modifiez config/packages/security.yaml, pour ajouter :

security:
    # ...
    firewalls:
        # ...
        main:
            stateless: true
            access_token:
                token_handler: App\Security\AccessTokenHandler
            # ...

Placez ensuite le contenu suivant dans src/Security/AccessTokenHandler.php (source: documentation officielle):

<?php
// src/Security/AccessTokenHandler.php
namespace App\Security;

use App\Repository\UserRepository;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;

class AccessTokenHandler implements AccessTokenHandlerInterface
{
    public function __construct(
        private UserRepository $repository
    ) {
    }

    public function getUserBadgeFrom(string $accessToken): UserBadge
    {
        $user = $this->repository->findOneBy(["token" => $accessToken]);

        if (null === $user) {
            throw new BadCredentialsException('Invalid credentials.');
        }

        return new UserBadge($user->getUserIdentifier());
    }
}

Ajoutez à la section components de nelmio_api_doc.yaml :

securitySchemes:
    Bearer:
        type: http
        scheme: bearer

Vous pouvez désormais ajouter les attributs suivants à POST /api/message :

// Provient de Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted
#[IsGranted("ROLE_USER")]
// Provient de Nelmio\ApiDocBundle\Annotation\Security;
#[Security(name: "Bearer")]

Vous devriez maintenant voir apparaître un cadenas indiquant que la route nécessite l'identification, et un bouton en haut à droite permettant de s'identifier :

Association des utilisateurs aux messages

Utilisez à nouveau symfony console make:entity pour ajouter un champ user de type ManyToOne de Message vers User.

  • Modifiez le code de POST /api/message de manière à ce que l'utilisateur courant soit associé au message
  • Ajoutez l'utilisateur dans les champs de message_basic (à chaque fois qu'un message est récupéré, on pourra voir son e-mail)

Faites en sorte que PATCH et DELETE ne fonctionnent pas si l'utilisateur n'est pas identifié, et n'est pas propriétaire du message

Ajoutez l'utilisateur au groupe message_basic, de manière à ce que l'utilisateur apparaisse dans la recherche des messages.