This tutorial has a new version, check it out!

JWT: Other Things to Think about

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $10.00

Mostly, that's it! JWT authentication is pretty cool: create an endpoint to fetch a token and an authenticator to check if that token is valid. With the error handling we added, this is a really robust system.

But, there are a few other things I want you to think about: things that you may want to consider for your situation.

Adding Scopes/Roles to the Token

First, when we created our JWT, we put the username inside of it:

... lines 1 - 12
class TokenController extends BaseController
{
... lines 15 - 18
public function newTokenAction(Request $request)
{
$user = $this->getDoctrine()
->getRepository('AppBundle:User')
->findOneBy(['username' => $request->getUser()]);
if (!$user) {
throw $this->createNotFoundException();
}
$isValid = $this->get('security.password_encoder')
->isPasswordValid($user, $request->getPassword());
if (!$isValid) {
throw new BadCredentialsException();
}
$token = $this->get('lexik_jwt_authentication.encoder')
->encode(['username' => $user->getUsername()]);
return new JsonResponse(['token' => $token]);
}
}

Later, we used that to query for the User object:

... lines 1 - 19
class JwtTokenAuthenticator extends AbstractGuardAuthenticator
{
... lines 22 - 48
public function getUser($credentials, UserProviderInterface $userProvider)
{
$data = $this->jwtEncoder->decode($credentials);
if ($data === false) {
throw new CustomUserMessageAuthenticationException('Invalid Token');
}
$username = $data['username'];
return $this->em
->getRepository('AppBundle:User')
->findOneBy(['username' => $username]);
}
... lines 63 - 99
}

But, you can put any information in your token. In fact, you could also include "scopes" - or "roles" to use a more Symfony-ish word - inside your token. Also, nobody is forcing your authenticator to load a user from the database. To get really crazy, you could decode the token and create some new, non-entity User object, and populate it entirely from the information inside of that token.

And really, not everyone issues tokens that are related to a specific user in their system. Sometimes, tokens are more like a package of permissions that describe what an API client can and can't do. This is a powerful idea.

OAuth versus JWT

And what about OAuth? If you've watched our OAuth tutorial, then you remember that OAuth is just a mechanism for securely delivering a token to an API client. You may or may not need OAuth for your app, but if you do use it, you still have the option to use JSON web tokens as your bearer, or access tokens. It's not an OAuth versus JWT thing: each accomplishes different goals.

Refresh Tokens

Finally, let's talk refresh tokens. In our app, we gave our tokens a lifetime of 1 hour:

... lines 1 - 72
lexik_jwt_authentication:
... lines 74 - 76
token_ttl: 3600

You see, JWT's aren't supposed to last forever. If you need them to, you might choose to issue a refresh token along with your normal access token. Then later, an API client could send the refresh token to the server and exchange it for a new JWT access token. Implementing this is pretty easy: it involves creating an extra token and an endpoint for exchanging it later. Auth0 - a leader in JWT - has a nice blog post about it.

Ok! If you have any questions, let me know. I know this stuff can be crazy confusing! Do your best to not overcomplicate things.

And as always, I'll see you guys next time.

Leave a comment!

This tutorial uses an older version of Symfony. The concepts of API tokens & JWT are still valid, but integration in newer Symfony versions may be different.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.0.*", // v3.0.3
        "doctrine/orm": "^2.5", // v2.5.4
        "doctrine/doctrine-bundle": "^1.6", // 1.6.2
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
        "symfony/swiftmailer-bundle": "^2.3", // v2.3.11
        "symfony/monolog-bundle": "^2.8", // v2.10.0
        "sensio/distribution-bundle": "^5.0", // v5.0.4
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.14
        "incenteev/composer-parameter-handler": "~2.0", // v2.1.2
        "jms/serializer-bundle": "^1.1.0", // 1.1.0
        "white-october/pagerfanta-bundle": "^1.0", // v1.0.5
        "lexik/jwt-authentication-bundle": "^1.4" // v1.4.3
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.0.6
        "symfony/phpunit-bridge": "^3.0", // v3.0.3
        "behat/behat": "~3.1@dev", // dev-master
        "behat/mink-extension": "~2.2.0", // v2.2
        "behat/mink-goutte-driver": "~1.2.0", // v1.2.1
        "behat/mink-selenium2-driver": "~1.3.0", // v1.3.1
        "phpunit/phpunit": "~4.6.0", // 4.6.10
        "doctrine/doctrine-fixtures-bundle": "^2.3" // 2.3.0
    }
}