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

    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ù
    Come far comparire il tuo brand su ChatGPT nel 2025: risposte semplici alle domande comuni

    Perché ChatGPT cita alcuni brand e non altri? Semplice: l'AI non ha opinioni proprie, ma si basa su informazioni pubbliche.

    Leggi di più
    Perché il mio sito non compare su Google? 5 errori che stai (forse) facendo

    Hai investito in un sito web professionale, magari anche bello da vedere… ma i contatti non arrivano, le vendite sono ferme e la tua presenza online sembra un po’ troppo silenziosa. Ti suona familiare?

    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