Molte volte nella tua applicazione potresti avere un sistema di login basato sul token JWT.
Ad un certo punto la tua applicazione potrebbe avere bisogno di rispondere ad una richiesta api fatta da un’altra applicazione.
Quindi potresti dare la possibilità di effettuare il login tramite il token JWT, ma questa procedura non è sicura .
Potrebbe essere necessario allora implementare un sistema di autenticazione tramite token, ma questo vorrebbe dire che la tua applicazione dovrebbe gestire entrambi i sistemi di autenticazione: JWT e il token attraverso api.

Ebbene, come puoi utilizzare entrambi i sistemi di autenticazione nella tua applicazione?

Il primo procedimento è fare in modo che il principale sistema di autenticazione funzioni come se fosse unico.

security.yml

rest_api:
pattern: ^/api/
stateless: true
guard:
    authenticators:
        - lexik_jwt_authentication.jwt_token_authenticator

Dopodiché bisogna implementare il secondo autenticatore dentro il file security.yml come di seguito:

rest_api:
pattern: ^/api/
stateless: true
guard:
    authenticators:
       - lexik_jwt_authentication.jwt_token_authenticator
       - AppBundle\Security\ApiKeyAuthenticator
    entry_point: lexik_jwt_authentication.jwt_token_authenticator

Con questa configurazione sono stati specificati due sistemi differenti di autenticazione e un “entry point”
Da notare che il parametro “entry point” è obbligatorio! Questo parametro rappresenta il principale sistema di autenticazione.

Ora bisogna aggiungere un campo chiamato ad esempio apiKey dentro la tabella degli utenti come di seguito:

/**
* @var string
*
* @ORM\Column(name="api_key", type="string", length=255, nullable=false, unique=true)
*/
private $apiKey;

Successivamente bisogna creare un autenticatore personalizzato per esempio:

namespace AppBundle\Security;

use AppBundle\Entity\PvpUser;
use Doctrine\ORM\EntityManagerInterface;
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\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Guard\AuthenticatorInterface;
use Symfony\Component\Security\Guard\Token\GuardTokenInterface;
use Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken;

class ApiKeyAuthenticator implements AuthenticatorInterface
{
    private $em;

    public function __construct(EntityManagerInterface $em)
    {
        $this->em = $em;
    }

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

     public function supports(Request $request)
     {
         return $request->headers->has('apikey');
     }

     public function getCredentials(Request $request)
     {
         return array(
             'token' => $request->headers->get('apikey'),
         );
      }

      public function getUser($credentials, UserProviderInterface $userProvider)
      {
          $apiKey = $credentials['token'];

          if (null === $apiKey) {
              return;
          }

          $user = $this->em->getRepository(User::class)->findOneByApiKey($apiKey);

          if (null == $user) {
              return null;
          }

          return $userProvider->loadUserByUsername($user->getUsername());
      }

      public function checkCredentials($credentials, UserInterface $user)
      {
           //here you can check password if necessary
           return true;
      }

      public function createAuthenticatedToken(UserInterface $user, $providerKey)
      {
           return new PostAuthenticationGuardToken(
                $user,
                $providerKey,
                $user->getRoles()
           );
      }

      public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
      {
          return new Response('Authentication Failure', 401);
      }

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

      public function supportsRememberMe()
      {
          return false;
      }
}

Spiegazione delle funzioni

start
È chiamata quando una richiesta anonima accede ad una risorsa che richiede autenticazione. Il compito di questo metodo è di ritornare una risposta che aiuti l’utente ad iniziare il processo di autenticazione.
Deve ritornare un oggetto di tipo Response

supports
Questa funziona indica se l’autenticatore supporta la richiesta (Request object) ricevuta
Deve ritornare un valore di tipo boolean

getCredentials
Questa funzione prende le credenziali di autenticazione dalla richiesta e le restituisce in un array o variabile, questo dipende dal tuo sistema

getUser
Ritorna un oggetto di tipo UserInterface basato sulle credenziali.
Deve ritornare un oggetto di tipo UserInterface o null.

checkCredentials
Ritorna true se le credenziali sono valide.
Deve ritornare un valore di tipo boolean

createAuthenticatedToken
Crea un token di autenticazione per l’utente dato.
Deve ritornare un oggetto di tipo GuardTokenInterface

onAuthenticationFailure
Questa funzione è chiamata quando l’autenticazione è stata eseguita ma è fallita (esempio apikey errato).
Deve ritornare un oggetto di tipo Response o null.

onAuthenticationSuccess
Questa funziona è chiamata quando l’autenticazione è stata eseguita con successo.
Deve ritornare un oggetto di tipo Response o null.

supportsRememberMe
Questa funzione indica se l’autenticazione supporta la funzione “remember me”.
Deve ritornare un valore di tipo boolean

In questo codice viene controllata se in ogni richiesta è presente negli header la chiave apikey.
Se questo header esiste, cerca di caricare un utente tramite il suo apikey all’interno del database. Se l’utente è stato trovato gli viene concesso il permesso di accedere alla risorsa richiesta.

In questo modo si possono utilizzare entrambi i sistemi di autenticazione per tutte le rotte.
Ma, se c’è la necessità di far accedere solo ad alcune rotte per un sistema di autenticazione, questo sistema si può gestire tramite il file security.yml

Alessandro Minoccheri
Written by Alessandro Minoccheri