Dans ce chapitre je vous propose de revenir sur le composant Security et on va découvrir comment créer un système d'authentification stateless pour notre partie API.
Le composant Security est composé de plusieurs éléments :
- Le UserProvider permet de trouver un utilisateur à partir de son identifier.
- L'Authenticator qui lui se charge d'authentifier l'utilisateur lors de certaines requête.
Dans le cadre d'une API, on aura en général une entête particulière Authorization
qui nous permet d'identifier l'utilisateur qui est à l'origine de la requête.
<?php
namespace App\Security;
use Symfony\Component\HttpFoundation\JsonResponse;
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\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
class APIAuthenticator extends AbstractAuthenticator
{
public function supports(Request $request): ?bool
{
return $request->headers->has('Authorization') && str_contains($request->headers->get('Authorization'), 'Bearer ');
}
public function authenticate(Request $request): Passport
{
$identifier = str_replace('Bearer ', '', $request->headers->get('Authorization'));
return new SelfValidatingPassport(
new UserBadge($identifier)
);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
return null;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
{
return new JsonResponse([
'message' => $exception->getMessage()
], Response::HTTP_UNAUTHORIZED);
}
}
Dans notre cas, on crée un passeport qui contient comme identifier la clef d'API. Il faudra ensuite modifier la configuration pour ajouter cet authenticator sur les URLs qui commencent par /api
.
security:
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
providers:
api_user_provider:
entity:
class: App\Entity\User
property: apiToken
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
api:
pattern: ^/api/
provider: api_user_provider
custom_authenticator: App\Security\APIAuthenticator
stateless: true
lazy: true
Dans mon cas, je me contente de chercher l'utilisateur qui a la propriété apiToken
qui correspond à la clef d'authorization. Pour des cas plus complexes, vous pouvez utiliser un provider personnalisé.