Autenticazione API/REST con JWT in Symfony

Tutorial Symfony per creare con JWT dei token json utili ad esempio in chiamate restful verso API protette da autenticazione

Introduzione

Innanzitutto andiamo a definire il concetto di JWT, acronimo di Json Web Token.
Il JWT è un JSON-based open standard (RFC 7519 – JSON Web Token (JWT)) per creare access token, decisamente utili per architetture API-REST in cui le comunicazioni sono stateless cioè il server non conosce nulla del client e tutte le informazioni devono risedere nel client.
Per esempio, a seguito di una login eseguita correttamente, il server può generare e restituire al client un token che contiene l’informazione “logged as admin”. Il client, nelle chiamate successive, potrà utilizzare (cioè inviare) questo token per indicare al server di essere loggato con privilegi di admin.
I token sono firmati con la chiave del server che potrà quindi verificarne la legittimità e integrità.
I token sono particolarmente compatti quindi utilizzabili in particolare nei contesti di single sign-on (SSO).

Implementazione

Ormai siamo abituati che nell’ecosistema di Symfony si trova quasi sempre il bundle corretto per le proprie esigenze.
Anche questa volta le aspettative non vengono tradite: sembra che il bundle LexikJWTAuthenticationBundle sia proprio fatto apposta per le nostre esigenze.
Tralasciamo le solite indicazioni di installazione che sono ben specificate nella documentazione e che non presentano nulla di particolare o difficile e veniamo al sodo.
Implementiamo quindi il meccanismo base di autenticazione a dei servizi Web API REST/Json avvalendoci di questo bundle e del comodissimo firewall Guard di Symfony.
Per questa semplice guida andremo a soffermarci solo sulla parte server-side che è il motore del sistema.

  1. Creiamo un metodo che gestisce l’autenticazione dell’utente nel controller dedicato alle API.
...

/**
 * @Route("/api")
 */
class DefaultController extends Controller
{
    /**
     * @Route(path="/token-authentication", name="api_token_authentication")
     */
    public function tokenAuthentication(Request $request)
    {
        $username = $request->request->get('username');
        $password = $request->request->get('password');

        $user = $this->getDoctrine()->getRepository('FooUserBundle:User')
            ->findOneBy(['username' => $username]);

        if (!$user) {
            throw $this->createNotFoundException(sprintf('Utente %s non trovato.', $username));
        }

        $userChecker = new UserChecker();
        $userChecker->checkPreAuth($user);

        if (!$this->get('security.password_encoder')->isPasswordValid($user, $password)) {
            throw $this->createAccessDeniedException(sprintf
('Dati di accesso per l\'utente %s non validi.', $username));
        }

        $userChecker->checkPostAuth($concorrente);

        // Use LexikJWTAuthenticationBundle to create JWT token that hold only information about user name
        $token = $this->get('lexik_jwt_authentication.jwt_encoder')->encode(['username' => $user->getUsername()]);

        // Return genereted token
        return new JsonResponse(['token' => $token]);
    }

Come vedete non è nulla di particolare, a fronte di uno username e password cerchiamo l’utente e se è valido, creiamo un token contenente lo username. Il token è creato dal servizio offerto dal bundle appena installato.

  1. Lato client, chiamiamo questo metodo…
curl -X POST http://localhost:8000/api/token-authentication -d _username=johndoe -d _password=test

… per ottenere in risposta il token.

{
   "token" : "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJleHAiOjE0MzQ3Mjc1MzYsInVzZX
JuYW1lIjoia29ybGVvbiIsImlhdCI6IjE0MzQ2NDExMzYifQ.nh0L_wuJy6ZKIQWh6OrW5hdL
kviTs1_bau2GqYdDCB0Yqy_RplkFghsuqMpsFls8zKEErdX5TYCOR7muX0aQvQxGQ4mpBkvMDhJ4-
pE4ct2obeMTr_s4X8nC00rBYPofrOONUOR4utbzvbd4d2xT_tj4TdR_0tsr91Y7VskCRFnoXAnNT-qQb7ci7HIBTbutb9zVStOFejrb4aLbr7Fl4byeIEYgp2Gd7gY"
}

  1. Creiamo quindi un firewall implementando l’interfaccia AbstractGuardAuthenticator
...
class JwtAuthenticator extends AbstractGuardAuthenticator
{
    private $em;
    private $jwtEncoder;

    public function __construct(EntityManager $em, JWTEncoder $jwtEncoder)
    {
        $this->em = $em;
        $this->jwtEncoder = $jwtEncoder;
    }

    public function start(Request $request, AuthenticationException $authException = null)
    {
        return new JsonResponse('Auth header required', 401);
    }

    public function getCredentials(Request $request)
    {
        if (!$request->headers->has('Authorization')) {
            return null;
        }

        $extractor = new AuthorizationHeaderTokenExtractor('Bearer');

        $token = $extractor->extract($request);

        if (!$token) {
            return null;
        }

        return $token;
    }

    public function getUser($credentials, UserProviderInterface $userProvider)
    {
        $data = $this->jwtEncoder->decode($credentials);

        if (!$data){
            return null;
        }

        $username = $data['username'];

        $user = $this->em->getRepository('FooUserBundle:User')
            ->findOneBy(['username' => $username]);

        if (!$user){
            return null;
        }

        return $user;
    }

    public function checkCredentials($credentials, UserInterface $user)
    {
        return true;
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
    {
        return new JsonResponse([
            'message' => $exception->getMessage()
        ], 401);
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    {
        return;
    }

    public function supportsRememberMe()
    {
        return false;
    }
}

Da notare che questo firewall entra in funzione solo per chiamate che presentano l’header “Authorization” che deve contenere il token precedentemente generato.
Inoltre il firewall non verifica le credenziali, dà per scontato che il token sia già di per sé, nella sua interezza e validità, l’elemento che garantisce l’autenticazione.
Quindi, in estrema sintesi, se il token è valido, il firewall si preoccupa solo di estrarne lo username e, da qui, eventualmente, caricare l’utente o i servizi necessari per gestire la chiamata.
Ricordiamoci infine di registrare il firewall in security.yml:

security:
      firewalls:
          api:
            pattern: ^/api
            anonymous: ~
            guard:
                authenticators:
                    - api.jwt_token_authenticator
    access_control:
          - { path: ^/api/token-authentication, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/api, roles: [ROLE_USER] }

Riferimenti

Richiedi Preventivo

    Acconsento al trattamento dei miei dati personali come da informativa sulla privacy di cui ho preso visione.

    Articoli recenti

    Compra su ChatGPT: la rivoluzione del Checkout Istantaneo

    OpenAI lancia “Buy it in ChatGPT”, la funzione che permette di acquistare prodotti direttamente nella chat. Scopri come funziona il Checkout Istantaneo, il ruolo di Stripe e l’impatto del nuovo Agentic Commerce Protocol sul futuro dell’e-commerce.

    Leggi di più
    WooCommerce vs PrestaShop: quale piattaforma scegliere per il tuo e-commerce?

    Quando si tratta di creare un e-commerce professionale, una delle domande più frequenti è questa: meglio WooCommerce o PrestaShop?

    Leggi di più
    Social Commerce: vendi dai social, proteggi il tuo business

    Nel 2025 vendere online è più semplice che mai grazie all’enorme diffusione del Social Commerce, ma allo stesso tempo è diventato più rischioso se non costruisci una strategia digitale solida e sostenibile.

    Leggi di più

    © 2024 WEB AGENCY MILANO FUTURAWEB SRL – VIA GIUSEPPE FRUA, 19 – 20146 MILANO / P.I. 03585460961 / REA 1686213 / CS 10000€ IV / PRIVACY

    crossmenuchevron-down
    linkedin facebook pinterest youtube rss twitter instagram facebook-blank rss-blank linkedin-blank pinterest youtube twitter instagram