Buy

JWT Guard Authenticator (Part 1)

To create our token authentication system, we'll use Guard.

Guard is part of Symfony's core security system and makes setting up custom auth so easy it's actually fun.

Creating the Authenticator

In AppBundle, create a new Security directory. Inside add a new class: JwtTokenAuthenticator:

63 lines src/AppBundle/Security/JwtTokenAuthenticator.php
... lines 1 - 2
namespace AppBundle\Security;
... lines 4 - 11
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
class JwtTokenAuthenticator extends AbstractGuardAuthenticator
{
... lines 16 - 61
}

Every authenticator starts the same way: extend AbstractGuardAuthenticator. Now, all we need to do is fill in the logic for some abstract methods. To get us started quickly, go to the "Code"->"Generate" menu - command+N on a Mac - and select "Implement Methods". Select the ones under Guard:

63 lines src/AppBundle/Security/JwtTokenAuthenticator.php
... lines 1 - 7
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;
... lines 12 - 13
class JwtTokenAuthenticator extends AbstractGuardAuthenticator
{
public function getCredentials(Request $request)
{
... lines 18 - 30
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
// TODO: Implement getUser() method.
}
public function checkCredentials($credentials, UserInterface $user)
{
// TODO: Implement checkCredentials() method.
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
// TODO: Implement onAuthenticationFailure() method.
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
// TODO: Implement onAuthenticationSuccess() method.
}
public function supportsRememberMe()
{
// TODO: Implement supportsRememberMe() method.
}
... lines 57 - 61
}

Tip

Version 2 of LexikJWTAuthenticationBundle comes with an authenticator that's based off of the one we're about to build. Feel free to use it instead of building your own... once you learn how it works.

Now, do that one more time and also select the start() method. That'll put start() on the bottom, which will be more natural:

63 lines src/AppBundle/Security/JwtTokenAuthenticator.php
... lines 1 - 5
use Symfony\Component\HttpFoundation\Request;
... lines 7 - 8
use Symfony\Component\Security\Core\Exception\AuthenticationException;
... lines 10 - 13
class JwtTokenAuthenticator extends AbstractGuardAuthenticator
{
... lines 16 - 57
public function start(Request $request, AuthenticationException $authException = null)
{
// TODO: Implement start() method.
}
}

If this is your first Guard authenticator... welcome to party! The process is easy: we'll walk through each method and just fill in the logic. But if you want to know more - check out the Symfony security course.

getCredentials()

First: getCredentials(). Our job is to read the Authorization header and return the token - if any - that's being passed. To help with this, we can use an object from the JWT bundle we installed earlier: $extractor = new AuthorizationHeaderTokenExtractor(). Pass it Bearer - the prefix we're expecting before the actual token - and Authorization, the header to look on:

63 lines src/AppBundle/Security/JwtTokenAuthenticator.php
... lines 1 - 13
class JwtTokenAuthenticator extends AbstractGuardAuthenticator
{
public function getCredentials(Request $request)
{
$extractor = new AuthorizationHeaderTokenExtractor(
'Bearer',
'Authorization'
);
... lines 22 - 30
}
... lines 32 - 61
}

Grab the token with $token = $extractor–>extract() and pass it the $request:

63 lines src/AppBundle/Security/JwtTokenAuthenticator.php
... lines 1 - 17
$extractor = new AuthorizationHeaderTokenExtractor(
'Bearer',
'Authorization'
);
$token = $extractor->extract($request);
... lines 24 - 63

If there is no token, return null:

63 lines src/AppBundle/Security/JwtTokenAuthenticator.php
... lines 1 - 17
$extractor = new AuthorizationHeaderTokenExtractor(
'Bearer',
'Authorization'
);
$token = $extractor->extract($request);
if (!$token) {
return;
}
... lines 28 - 63

This will cause authentication to stop. Not fail, just stop trying to authenticate the user via this method.

If there is a token, return it!

63 lines src/AppBundle/Security/JwtTokenAuthenticator.php
... lines 1 - 17
$extractor = new AuthorizationHeaderTokenExtractor(
'Bearer',
'Authorization'
);
$token = $extractor->extract($request);
if (!$token) {
return;
}
return $token;
... lines 30 - 63

getUser()

Next, Symfony will call getUser() and pass this token string as the $credentials argument. Our job here is to use that token to find the user it relates to.

And this is where JSON web tokens really shine. Because if we simply decode the token, it will contain the username. Then, we can just look it up in the database.

To do this, we'll need two services. On top of the class, add a __construct() method so we can inject these. First, we need the lexik encoder service. Go back to your terminal and run:

./bin/console debug:container lexik

Select the lexik_jwt_authentication.encoder service. Ah, this is just an alias for the first service - lexik_jwt_authentication.jwt_encoder. And this is an instance of JWTEncoder. Back in the authenticator, use this as the type-hint. Or wait, since it looks like there's an interface this probably implements, you can use JWTEncoderInterface instead. Give this one more argument: EntityManager $em:

84 lines src/AppBundle/Security/JwtTokenAuthenticator.php
... lines 1 - 16
class JwtTokenAuthenticator extends AbstractGuardAuthenticator
{
... lines 19 - 21
public function __construct(JWTEncoderInterface $jwtEncoder, EntityManager $em)
{
... lines 24 - 25
}
... lines 27 - 82
}

I'll use a shortcut - option+enter on a Mac - to initialize these fields:

84 lines src/AppBundle/Security/JwtTokenAuthenticator.php
... lines 1 - 16
class JwtTokenAuthenticator extends AbstractGuardAuthenticator
{
private $jwtEncoder;
private $em;
public function __construct(JWTEncoderInterface $jwtEncoder, EntityManager $em)
{
$this->jwtEncoder = $jwtEncoder;
$this->em = $em;
}
... lines 27 - 82
}

This created the two properties and set them for me. Nice!

Head back down to getUser(). First: decode the token. To do that, $data = $this–>jwtEncoder->decode() and pass it $credentials - that's our token string:

84 lines src/AppBundle/Security/JwtTokenAuthenticator.php
... lines 1 - 16
class JwtTokenAuthenticator extends AbstractGuardAuthenticator
{
... lines 19 - 43
public function getUser($credentials, UserProviderInterface $userProvider)
{
$data = $this->jwtEncoder->decode($credentials);
... lines 47 - 56
}
... lines 58 - 82
}

That's it! $data is now an array of whatever information we originally put into the token. Fundamentally, this works just like a normal json_decode, except that the library is also checking to make sure that the contents of our token weren't changed. It does this by using our private key. This guarantees that nobody has changed the username to some other username because they're a jerk. Encryption is amazing.

It also checks the token's expiration: our tokens last 1 hour because that's what we setup in config.yml:

77 lines app/config/config.yml
... lines 1 - 72
lexik_jwt_authentication:
... lines 74 - 76
token_ttl: 3600

So, if ($data === false), then we know that there's a problem with the token. If there is, throw a new CustomUserMessageAuthenticationException() with Invalid token:

84 lines src/AppBundle/Security/JwtTokenAuthenticator.php
... lines 1 - 45
$data = $this->jwtEncoder->decode($credentials);
if ($data === false) {
throw new CustomUserMessageAuthenticationException('Invalid Token');
}
... lines 51 - 84

Tip

In version 2 of the bundle, you should instead use a try-catch around this line:

use Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTDecodeFailureException;
// ...

public function getUser($credentials, UserProviderInterface $userProvider)
{
    try {
        $data = $this->jwtEncoder->decode($credentials);
    } catch (JWTDecodeFailureException $e) {
        // if you want to, use can use $e->getReason() to find out which of the 3 possible things went wrong
        // and tweak the message accordingly
        // https://github.com/lexik/LexikJWTAuthenticationBundle/blob/05e15967f4dab94c8a75b275692d928a2fbf6d18/Exception/JWTDecodeFailureException.php

        throw new CustomUserMessageAuthenticationException('Invalid Token');
    }

    // ...
}

We'll talk about what that does in a second.

But if everything is good, get the username with $username = $data['username']:

84 lines src/AppBundle/Security/JwtTokenAuthenticator.php
... lines 1 - 45
$data = $this->jwtEncoder->decode($credentials);
if ($data === false) {
throw new CustomUserMessageAuthenticationException('Invalid Token');
}
$username = $data['username'];
... lines 53 - 84

Then, query for and return the user with return $this–>em–>getRepository('AppBundle:User')–>findOneBy() passing username set to $username:

84 lines src/AppBundle/Security/JwtTokenAuthenticator.php
... lines 1 - 45
$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 57 - 84

checkCredentials()

If the user is not found, this will return null and authentication will fail. But if a user is found, then Symfony finally calls checkCredentials(). Just return true:

84 lines src/AppBundle/Security/JwtTokenAuthenticator.php
... lines 1 - 16
class JwtTokenAuthenticator extends AbstractGuardAuthenticator
{
... lines 19 - 58
public function checkCredentials($credentials, UserInterface $user)
{
return true;
}
... lines 63 - 82
}

There's no password or anything else we need to check at this point.

And that's it for the important stuff!

Skip Everything Else (for now)

Skip onAuthenticationFailure() for now. And for onAuthenticationSuccess(), purposefully do nothing:

84 lines src/AppBundle/Security/JwtTokenAuthenticator.php
... lines 1 - 16
class JwtTokenAuthenticator extends AbstractGuardAuthenticator
{
... lines 19 - 63
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
// do nothing - let the controller be called
}
... lines 73 - 82
}

We want the authenticated request to continue to the controller so we can do our normal work.

In supportsRememberMe() - this doesn't apply to us - so return false:

84 lines src/AppBundle/Security/JwtTokenAuthenticator.php
... lines 1 - 16
class JwtTokenAuthenticator extends AbstractGuardAuthenticator
{
... lines 19 - 73
public function supportsRememberMe()
{
return false;
}
... lines 78 - 82
}

And keep start() blank for another minute. With just getCredentials() and getUser() filled in, our authenticator is ready to go. Let's hook it up!

Leave a comment!

  • 2017-08-21 Rajesh Patel

    thanks...now its working

  • 2017-08-21 Victor Bocharsky

    Hey Rajesh,

    Do you implement UserInterface in your AppBundle\Entity\User entity? Because looks like you don't regarding this error. Or, maybe you just forget to use namespace for UserInterface. Make sure you have "use Symfony\Component\Security\Core\User\UserInterface" namespace in JwtTokenAuthenticator.

    Cheers!

  • 2017-08-21 Rajesh Patel

    I am getting this error
    The AppBundle\Security\JwtTokenAuthenticator::getUser() method must return a UserInterface. You returned AppBundle\Entity\User.

  • 2017-05-01 weaverryan

    Hey Binarymayhem!

    You are definitely understanding the JWT philosophy correctly! So yes, we don't technically need to hit the database to verify the user, we're doing it for a different reason :). In this example, all we store in the token is the username. But, for Symfony's security to work, we need the full user *object*. The database call is just to take that username and go query for all of the fresh User data (i.e. so we get the full user object).

    In other situations, it might make more sense for you to include more than the username in the JWT - e.g. you might include their email, roles, etc. Then, you could *totally* use that information to create an entire User object without querying the database. It just depends on your scenario :). Most of the time, people store their Users in the database and expect that when they say $this->getUser() to fetch the User object, that it is their User entity. But in a more sophisticated setup, you could make your User object just a normal class (not a Doctrine entity), and then create the User object from the JWT as described. This would allow you to have very fine-grained controls in your JWT (for example, you could create different tokens with different roles).

    And you're right about the sentence - this is definitely signing! I was a bit lazy on my words there, but I hope the correct sentiment comes through.

    Cheers!

  • 2017-04-26 Binarymayhem

    Thanks for the example, it definitely clarified a few questions I had as far as how guard was working.

    Since we are utilizing jwt, is there really any benefit with a trip back to the db to verify the user since its already in the jwt claim? Couldn’t we just verify the claim was OK by calling $token->verify($signer, ‘secret') and just letting the request flow though. Is there another class (instead of AbstractGuardAuthenticator) that would be better reflect said example. I’m still very new to Silex (using the following symfony components: guard/security/doctrine) and am struggling to find a solution to my example.

    Also I just wanted to point out that the following sentence is incorrect: “This guarantees that nobody has changed the username to some other username because they're a jerk. Encryption is amazing” I think what you meant to say was “Signing is amazing”. The data is technically not encrypted since we can still read it, however it is signed so we can determine if the contents have been tampered with on the client side or in transit.

  • 2016-10-23 weaverryan

    Hey Johan!

    Good fix! The new version of the LexikJWTAuthenticationBundle (released just a couple of days ago) should fix this problem (another awesome user reported it here, and the bundle maintainer fixed it quickly). Yes open source!

    A few other minor things may be different if you use the LexikJWTAuthenticationBundle 2.* (we use 1.* in the tutorial) - I have it on my list to add any notes necessary to our tutorial to mention those. So far, the only other issue I know of is talked about here: https://knpuniversity.com/s...

    Cheers!

  • 2016-10-21 Johan

    In Symfony 3.1.5 autowiring does not work. It's not sure which service to pick: the default encoder or the lcobucci encoder (whatever that is...).

    I fixed it though by just manually wiring the arguments :)

  • 2016-07-01 Roukmoute

    If you have this error like:

    PHP Fatal error: Class AppBundle\Security\JwtTokenAuthenticator contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface::start) in src/AppBundle/Security/JwtTokenAuthenticator.php on line 16

    It's only because, GuardAuthenticatorInterface extends with another AuthenticationEntryPointInterface, so just implement the missing method called `start` (\Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface::start)

    If you don't care about this, just do a `return;`

    For more informations, reads commentary about this method:

    This is called when an anonymous request accesses a resource that requires authentication, when the user is not authenticated at all.
    It returns generally a new Response.
    It's for helping the user to connect to the website (so redirect to a form or use a header with an API for example)

    Bye!