Blogpost

Passage of time

3 minute read

Authentication API Client

Let an API use the Json Web Token to retrieve user information.

In the previous post we have setup an authentication API and we have added the refresh-token feature. Now it’s time to let our clients use the received JWT to retrieve user information.

Clients can of course be written in all languages. To focus on the logic more then the code itself, this client is another symfony 4 application.

At it’s most basic form, clients just need to know if the JWT is valid or not. Therefore, the only thing we need is the JWT Public key from the Authentication API.

This means you can use composer require lexik/jwt-authentication-bundle again, but with a minor difference in configuration, as the client will only validate the JWT Token, but will not generate a JWT token. No need in generating a new private key, or setting it in the configuration.

Configuration for lexik jwt:

lexik_jwt_authentication:
   private_key_path: ''
   public_key_path: '%kernel.project_dir%/%env(JWT_PUBLIC_KEY_PATH)%'
   pass_phrase: '%env(JWT_PASSPHRASE)%'

We need a user entity, so let’s use the default JWTUser, to keep this as simple as possible.

Setup security.yaml:

security:
   providers:
       jwt_user_provider:
           lexik_jwt:
               class: Lexik\Bundle\JWTAuthenticationBundle\Security\User\JWTUser
   firewalls:
       dev:
           pattern: ^/(_(profiler|wdt)|css|images|js)/
           security: false
       main:
           provider: jwt_user_provider
           stateless: true
           guard:
               authenticators:
                   - lexik_jwt_authentication.jwt_token_authenticator

And there you have it. When a user or client connects to this API using JWT, the API can check if the user has been validated by the authentication API, and can even get it’s id and roles. Easy peasy!

Of course this setup is VERY basic. You can add more user details to your JWT token, such as email, username, first & lastname etc, but always bare in mind not to overload your JWT tokens with data that is irrelevant for simple authentication.

If you do add information to the JWT, you can add an extra User Entity to your API Client ( and replace the JWTUser class in configuration with your custom user). That user should of course implement the JWTUserInterface and UserInterface, and have a createFromPayload method to decode the JWT into the user Entity.

In the case of a closed ecosystem, where users are actually always the same entity, it would be advisable to move the user Entity, with its serialization to a dedicated bundle, so you can serialize the user into the JWT in the authentication API, and deserialize the JWT back into the user Entity on the client side. Unfortunatly, deserialization is not so simple.


To deserialize JWT data into a User entity, you will need a custom UserProvider and a custom authenticator. The Authenticator would look something like :

class TokenAuthenticator extends JWTTokenAuthenticator
{
    /**
     * Loads the user to authenticate.
     *
     */
    protected function loadUser(UserProviderInterface $userProvider, array $payload, $identity)
    {
        if ($userProvider instanceof UserProvider) {
            return $userProvider->loadUserByUsername($identity, $payload);
        }

        return $userProvider->loadUserByUsername($identity);
    }
}

and the userprovider:

class UserProvider implements UserProviderInterface
{
    /**
     * @var SerializerInterface
     */
    private $serializer;

    /**
     * UserProvider constructor.
     * @param SerializerInterface $serializer
     */
    public function __construct(SerializerInterface $serializer)
    {
        $this->serializer = $serializer;
    }

    public function loadUserByUsername($username, array $payload = [])
    {
        if (isset($this->cache[$username])) {
            return $this->cache[$username];
        }
        $this->cache[$username] = $this->serializer->deserialize(json_encode($payload), User::class, 'json', ['groups' => ['public']]);
        return $user;
    }

Don’t forget to pass the serializer to the UserProvider in services.yaml:

    App\Security\UserProvider:
         arguments:
             - '@serializer'

So, final state: Users or clients can request a JWT token from the Authentication API and client API’s can check if the JWT Token is valid AND reconstruct the user entity. Mission accomplished.

The authentication API as it is now is of course not very useful. Next step would be to add calls so clients can create, update, delete or fetch user information. Stay tuned for more.