Customising Your Encoded JWT


Another really useful tool to have in your box is being able to customise the data output in the JWT.

Whenever a user logs in, or successfully registers, they will receive this JWT - a JSON Web Token.

This token contains encoded information.

This is data that - crucially - is available to the front end / API consumer without them having to directly query for it.

If you do any front end work, you will quickly indentify a problem: on anything but a trivial system, you will have some restriction of resources.

An example used in this course is the profile endpoint. You don't want just anyone being able to access any profile.

A commonly design feature is to display the username in some position on the page. When logged in to CodeReviewVideos, you will have your username displayed in the top right.

This is a common pattern which users instincitively recognise.

Therefore, it stands that the request for the profile information is going to be very common.

We may therefore choose to return the most commonly required fields - the id, and username, and possibly email - every time we issue a new JWT. This cuts down a potential source of noise.

We needn't be limited to what LexikJWTAuthenticationBundle provides us by default.

The default information actually contains useful stuff.

{
  "exp": 1478432444,
  "username": "peter",
  "iat": "1478346044"
}

This information contains the time the token was issued at (iat), and when it expires (exp).

It also, handily, contains our username. But not the id.

Let's add in the id.

We can either replace the entire payload - if you don't care about the exp, or iat:

<?php

// /src/AppBundle/Event/Listener/JWTCreatedListener.php

namespace AppBundle\Event\Listener;

use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTCreatedEvent;

class JWTCreatedListener
{
    /**
     * Replaces the data in the generated
     *
     * @param JWTCreatedEvent $event
     *
     * @return void
     */
    public function onJWTCreated(JWTCreatedEvent $event)
    {
        /** @var $user \AppBundle\Entity\User */
        $user = $event->getUser();

        // add new data
        $payload['userId'] = $user->getId();
        $payload['username'] = $user->getUsername();

        $event->setData($payload);
    }
}

Which would give the output similar to:

{
  "id": 1
  "username": "peter"
}

Or, if you do care about the exp, and iat:

<?php

// /src/AppBundle/Event/Listener/JWTCreatedListener.php

namespace AppBundle\Event\Listener;

use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTCreatedEvent;

class JWTCreatedListener
{
    /**
     * Adds additional data to the generated JWT
     *
     * @param JWTCreatedEvent $event
     *
     * @return void
     */
    public function onJWTCreated(JWTCreatedEvent $event)
    {
        /** @var $user \AppBundle\Entity\User */
        $user = $event->getUser();

        // merge with existing event data
        $payload = array_merge(
            $event->getData(),
            [
                'userId' => $user->getId()
            ]
        );

        $event->setData($payload);
    }
}

And of course you can add in any roles or whatever. Just be aware this data is encoded, not encrypted. Anything you put in here should be information you don't mind exposing to the public.

For more information on this subject, click here and read the docs.

This last feature draws an end to this course.

In this course we have covered one possible implementation that makes use of the existing FOSUserBundle workflows, but using JSON for interaction.

Many of the emails I get ask me whether I have examples of real world systems. These are the sorts of things you see out in the real world. There is a definite lack of beauty to this entire project - it relies heavily on copy / paste, the cardinal sin.

I would love to see something like this added by default into FOSUserBundle. As more of us switch to using Symfony for the back end, and insert modern JavaScript buzzword for our front end, I believe this is an improvement that can help a huge swathe of the Symfony community address a likely common concern.

To show one potential front end for this API, in the very next course we will cover how to build a "React, and Redux with Sagas" front end. Knowing both ends is a lovely way to become a massively improved programmer.

Thanks for watching, and as ever, please feel free to leave questions, comments, suggestions, etc. I really do appreciate it.

Code For This Course

Get the code for this course.

Episodes