Buy
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:

... 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:

... 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:

... 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:

... 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:

... lines 1 - 17
$extractor = new AuthorizationHeaderTokenExtractor(
'Bearer',
'Authorization'
);
$token = $extractor->extract($request);
... lines 24 - 63

If there is no token, return null:

... 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!

... 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:

... 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:

... 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:

... 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:

... 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:

... 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']:

... 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:

... 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:

... 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:

... 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:

... 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!

  • 2018-04-12 weaverryan

    Woohooo! Cheers!

  • 2018-04-12 nOograss

    Hi
    It was a mistake in my firewall configurarion. Its now working fine :)
    A big thanks for this course, learned a lot !

  • 2018-04-12 weaverryan

    Hey @noograss!

    I hope that angular module helped you! On the Symfony-side, hmmm, yea, I suppose you could use the remember_me with JWT :). The remember_me key under your firewall simply sets a remember me cookie. From an Angular front-end, if, for some reason, your AJAX requests are suddenly not authenticated (e.g. you no longer have the token or the token has expired), then naturally the remember me cookie should cause your requests to continue to be authenticated.

    Cheers!

  • 2018-04-06 nOograss

    Hello,

    Is it possible to use the rememberMe function when using jwt or do I have to use the built in version from Symfony2 ?

    How would you go about it ?
    [EDIT] Well angular as a module to remember the jwt, i'm gonna check this out héhé

  • 2018-01-09 macros

    thank you I really appreciate your help, your time, you made my day, you just give me a global easy and simple image, to understand :)
    1) I know that the lexical bundle takes care of validating the token, but here,do you think that is recommended to store the api key into the database for some reasons?
    2) Does the first Ajax request {username, password} is sent in clear?
    3) after getting the token in my front app, is it possible to decode it?

  • 2018-01-08 weaverryan

    Hey again @disqus_RxtRUjU2tw!

    Let me see if I can help :)

    1) When a request comes into your app, you will want to write/run some code that checks to see if that request contains some authentication information (e.g. an API token) and then authenticates the user based on that information. Especially in an API, you'll want this code to execute on every request and run *before* your controller (so that by the time your controller is executed, you're already authenticated). The Guard system is an easy way for you to add this code: create one class, fill in a few methods, and BAM! You've got an authentication system that you fully control. The lexik auth bundle *could* have done this work for you, but, if I remember correctly, they tell *you* to create the Guard authenticator so that you ultimately have full control over your API (including the response you send on auth error, auth success, etc).

    2) Another great question! Your "firewall" *is* your security system, and you typically only have one. For each firewall, you could configure multiple "authenticators". For example, suppose you have one firewall with 3 authenticators. At the beginning of each request, all 3 authenticators are called. This means that each authenticator has the opportunity to look for authentication information and (potentially) authenticate the user. One, somewhat real example would be to have 2 authenticators: (A) an JWT token authenticator that looks for a token on a X-API-Token header, (B) a http basic authenticator that looks for HTTP basic username+password to exist and authenticates based on that. Basically, you will have 1 authenticator per *method* of authentication that you want to offer your users. Many time, you will have just one, but you could have 10! And "Guard" is just the fancy name we gave to the "authenticator" system - don't give that term any special thought.

    3) This is the BEST question :). Yes, you need to create a provider. But, this is not really because you *need* it or will use it. Instead, it's more because Symfony's security system requires this to exist. For a traditional HTML app, you *do* need it (for some session-based logic). But, for an API, you really should *not* need it. So, create it (because it's required), but then stop thinking about it :).

    > [important!]What is the main reason of firewall, autheticator, guard and providers ?

    * Firewall = your entire security system. You will typically only have 1. But if you had 2, as the request enters your app, only 1 firewall is activated (based on the URL string). A firewall is typically not an important concept unless you have multiple (btw, the "dev" firewall does NOT count as a second firewall - ignore it - you really only have 1 real firewall in a normal Symfony app).

    * Authenticator: classes that run at the beginning of each request and attempt to authenticate the user based on different logic. All authenticators for the "active" firewall are called one every request.

    * Guard: the fancy word we gave to the authenticator system

    * providers: an unfortunately "required" thing you should just create, then forget about. Someday, we will hopefully make this optional!

    Cheers!

  • 2018-01-07 macros

    I'm trying to understing how to make an authentificaion system to my API
    so i installed lexik auth bundle, then for next steps i find my slef blocked cause i need some responses:

    1)why we need to use an authenticator ? and then why Guard ? what's your logic when you created Guard system?

    2)what is the relation between firewall, autheticator and guard ?

    3)Does we really to implement a provider and then point it under security.yml/providers ? in wish case of exemple authentication, we need to create providers ?
    [important!]What is the main reason of firewall, autheticator, guard and providers ?

    THANK YOU CHEERS

  • 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!

  • 2016-03-31 dlegatt

    I was just stripping the characters completely. I've seen functions to safely encode base64 for URLs by replacing them with - or ., but I don't have any need to ever decode the string, I just needed something unique and random to use as a key.

  • 2016-03-31 Jonathan Keen

    dlegatt,

    Thanks for that tip. I'm using PHP 7 so it's working wonderfully.

    Curious, are you using anything to replace the +, /, and = characters? Or just stripping them completely?

    I'm creating, checking if unique, and storing the API Token during the prepersist event for Doctrine, just in case of any collisions. Are you doing something similar?

    Love this little KNP University community that we have here.

  • 2016-03-31 Jose Ares

    Nice article. I have a basic question:

    When a user register, he/she enter for example mail & password, so how would the token be generated in the first place? If I'm not worng the cycle would be:

    1) User registers
    2) User log in with username & password
    3) If credentials are correct, a new token is generated (for that session only?) and saved in the DDBB
    4) From now on, every single HTTP request will add the "X-TOKEN" header with that value to maintain the user logged in
    5) User Logs out and the token is deleted from the DDBB

    Is that correct? Also, registering the authenticator in security.yml makes it hook in every request?

    Thanks very much!

  • 2016-03-25 dlegatt

    PHP has a Cryptographically Secure Pseudo-Random Number Generator (CSPRNG) as of PHP 7. This includes two functions, random_int and random_bytes. Symfony also includes a compatibility library from Paragon that provides compatibility for these functions on PHP 5.

    What I do to generate a token is base64_encode(random_bytes(64)). Then, just to make sure I don't have any problems sending as a URL or header, I use str_replace to remove any +, /, or = characters that may have been generated by the base64 encoding process.

  • 2016-02-15 weaverryan

    That's a "tricky" question in a sense - technically getting a true "random" number should probably include some sort of secret "key" that *you* provide. That always helps things being random. Of course, this is probably "random enough" - but your absolute best bet (as far as I can see) is to use Anthony Ferrara's library: https://github.com/ircmaxel.... He's a stickler for security, so using his library is a good way to go.

    And thanks for your kind words - that's really awesome - I love what I do because I get to work with people like you :).

    Cheers!

  • 2016-02-12 Jonathan Keen

    Ryan, have a question for you on generating the apiToken, in the course code you're using:

    $this->apiToken = base_convert(sha1(uniqid(mt_rand(), true)), 16, 36);

    Would you recommend this as a completely unique apiToken I can use to generate for my user upon creation? Sorry, not sure if my Google-fu is broken, but I can't seem to find a well thought out answer to creating an API token. Especially one that I don't have to check is unique before flushing with Doctrine (and then getting an error that the column is not unique.)

    Thanks very much in advance! It's nice having access to someone as experienced as you are. That alone is worth the subscription fee. The courses are almost a bonus, although I love them and cannot believe how they have skyrocketed my knowledge. You are a fabulous teacher.

  • 2016-02-12 DevDonkey

    ah awesome, many thanks Ryan.

    Cheers,

    Matt

  • 2016-02-12 weaverryan

    Hey!

    Ah, glad you like it! This tutorial (at this moment) is still about KnpUGuardBundle - the one in core has some slight differences. The big one is that checkCredentials() (in core) must return true. Returning anything else fails auth. We did that so that you really had to "opt-in" to "passing" the credentials check. So yes! Return true and you'll be good. And also, getUser() is called *before* checkCredentials() - so if you fail to return a User object from getUser(), checkCredentials will never be called (the order is getCredentials, getUser, checkCredentials.

    Cheers!

  • 2016-02-12 DevDonkey

    hi Ryan, great tutorial by the way. I love guard, it makes life so much simpler.

    Im implementing the api side of things and Ive come up against an issue.

    if checkCredentials() just returns (as in your example) auth fails, but if it returns true then successful auth is then dependent upon the getUser() method, which as far as I can see is desired behaviour?

    Have I got something wrong, or did you typo line 48?

    many thanks.

    Matt

  • 2015-09-14 weaverryan

    Hey Reynier!

    There's 2 things I can tell you :). First, a functional test is probably the best idea - one where you actually send a test HTTP request up to the server and test your authentication. Second, if you want to actually unit test your authenticator, then that should be easy: it's your class, so just test its methods like any normal class. For example, if you wanted to test that the header was being fetched off of the Request correctly in getCredentials(), just create a Request object in your test, give it a header, then call getCredentials() on your authenticator. You can then test that it returns the correct header value. You will need to use mocking for your authenticator's dependencies, but fortunately, this is all just normal testing stuff.

    Cheers!

  • 2015-09-12 Reynier Pérez Mira

    Hi there weaverryan I am trying to write some unit/functional test for this setup and have not idea at all, could you add to the tutorial how to achieve this using phpunit or any other tool? I would like to have my API fully tested ;)

  • 2015-08-21 weaverryan

    Keep stateless - that turns the session off, that's what you want. I don't know what your issue is, I'm sorry - I can't help this deep into your specific project - that's what my actual day job is :).

  • 2015-08-21 Reynier Pérez Mira

    weaverryan please take a look to this post: http://stackoverflow.com/qu... I don't know what else to do

  • 2015-08-21 Reynier Pérez Mira

    stateless is true you told me to put that value there ;) should I change it back to false?

  • 2015-08-21 weaverryan

    There's certainly no caching, so the only thing that could be causing anything that looks like caching would be if the user information were stored in the session. But since you have stateless: false on your firewall, I can't think of anything with caching. I'd add debugging code into your authenticator to see what's going on.

  • 2015-08-21 Reynier Pérez Mira

    Hi there weaverryan is there any chance that API token get's cached? I'm running a weird issue related to the repTokenId field since when it's value is NULL API still returning a valid value - which turns into invalid one, any advise?

  • 2015-08-07 weaverryan

    Awesome! Thanks for trying it out! I've opened an issue about it here: https://github.com/knpunive....

    Cheers!

  • 2015-08-07 Reynier Pérez Mira

    Excellent, I don't know what this is but your suggestion fix the issue, if you can add to docs ;) and I didn't found you on IRC :(

  • 2015-08-07 weaverryan

    Hey Reynier!

    You can find me on IRC Freenode as weaverryan :). Add a "stateless: true" key under your "api_area" firewall (so, at the same level as "pattern" and "knpu_guard") and tell me if you get the same issues. I know what the problem is, and I think this will fix it. Let me know - I'll need to (at least) make some updates to the documentation for this.

    Cheers!

  • 2015-08-07 Reynier Pérez Mira

    I'm not using any client at all, those request are being made from an iOs app. For testing purpose I use Postman at Chrome. I've found this topic at SO http://stackoverflow.com/qu... perhaps it will be helpful or doesn't at all. PS: Can we talk over chat? Hangouts? IRC? Or something else in order to get this working and after put the interesting part here or did you prefer still here chatting over question on Discuss?

  • 2015-08-07 weaverryan

    Hey Reynier!

    Actually, this is a good question/issue - and even could relate to a bug or something that's at least not documented yet related to sessions. What client are you using to make these API requests? Is it AJAX from a browser? Or something else?

    The issue appears to be that the user is being stored in the session (which we don't want/need for an API), and then it breaks after that (because of course, we're not coding in a way that expects this).

    Let me know and I should be able to help - and your answer may help the library too :)

    Cheers1

  • 2015-08-07 Reynier Pérez Mira

    Hi there weaverryan I am still having a little issues here and I'm not able to fix them. Some API calls are failing and I can see this on logs:


    [2015-08-07 11:39:04] request.INFO: Matched route "api_1_get_targets_activity". {"route_parameters":{"_controller":"PDI\\PDOneRestBundle\\Controller\\TargetRestController::getTargetsActivityAction","_format":"json","_route":"api_1_get_targets_activity"},"request_uri":"app_dev.php/api/v1/targets/activity?_format=json&tid=0018000000aFZ9fAAG"} []
    [2015-08-07 11:39:04] security.DEBUG: Read existing security token from the session. {"key":"_security_api_area"} []
    [2015-08-07 11:39:04] request.CRITICAL: Uncaught PHP Exception RuntimeException: "There is no user provider for user "PDI\PDOneBundle\Entity\Representative"." at /var/www/html/backendvendor/symfony/symfony/src/Symfony/Component/Security/Http/Firewall/ContextListener.php line 174 {"exception":"[object] (RuntimeException(code: 0): There is no user provider for user \"PDI\\PDOneBundle\\Entity\\Representative\". at /var/www/html/backend/vendor/symfony/symfony/src/Symfony/Component/Security/Http/Firewall/ContextListener.php:174)"} []

    What they means and how to fix it? Any ideas or advice?

  • 2015-07-29 weaverryan

    See https://knpuniversity.com/s... for how you can cause an authentication failure and control the messages.

    Are there 2 issues now?

    1) If no header is sent, then throw any AuthenticationException() in getCredentials() (except, of course, for the postSessionAction URL). This will require the client to *always* pass this header.

    2) If the credentials are wrong, and you won't want to control exactly which message is sent to your user, see my above link. You'll probably want to throw the same exception in getCredentials (if the header is missing) and getUser (if the token is bad). This will cause the error message to be the same in both cases.

    Cheers!

  • 2015-07-29 Reynier Pérez Mira

    Ok, that fixes that issue but now another comes out - perhaps not an issue but a miss understood from my side. If I not send

    X-PDONE-SESSION-ID

    request is allowed and end user can see a valid response - this is wrong and shouldn't be allowed -, ff I send an invalid

    X-PDONE-SESSION-ID

    I got

    {
    "message": "Authentication credentials could not be found."
    }

    , how this could be avoid? I don't want unwanted and unauthorized people sniffing API results

  • 2015-07-29 weaverryan

    Hey Reynier!

    Just remove the access_control for ^/api entirely. The authentication (in getCredentials()) is forcing authentication on all /api URLs (except for postSessionAction) by throwing the CredentialsNotFoundException. So, this access_control line is not needed, and in fact is causing our problem now :).

    Cheers!

  • 2015-07-29 Reynier Pérez Mira

    Yes I've seen your reply and test both ways:


    #using URL
    if ($request->getPathInfo() == '/api/v1/sessions') {
    return;
    }

    #or using route
    if ($request->attributes->get('_route') == 'api_1_post_session') {
    return;
    }

    This is how the complete method look likes:


    public function getCredentials(Request $request)
    {
    // skip authentication for this one endpoint
    // first way using URL
    if ($request->getPathInfo() == '/api/v1/sessions') {
    return;
    }

    // second way using routes
    /*if ($request->attributes->get('_route') == 'api_1_post_session') {
    return;
    }*/

    return $request->headers->get('X-PDONE-SESSION-ID');
    }

    In both cases I can't reach postSessionAction() I got an "AccessDeniedHttpException" all the time:


    {
    "error": {
    "code": 403,
    "message": "Forbidden",
    "exception": [
    {
    "message": "You do not have the necessary permissions",
    "class": "Symfony\\\\Component\\\\HttpKernel\\\\Exception\\\\AccessDeniedHttpException"
    }
    ]
    }
    }

    This is a piece of the security.yml file:


    firewalls:
    #this is the secured area accessed through web browser and only authenticated reps are allowed to use it
    api_area:
    pattern: ^/api
    anonymous: ~
    knpu_guard:
    authenticators:
    - app.api_token_authenticator

    #this looks the same as I show you before
    admin_area:
    ...

    access_control:
    - { path: ^/login, role: IS_AUTHENTICATED_FULLY }
    # Also tried the line below, no success
    # - { path: ^/api/v1/sessions, role: IS_AUTHENTICATED_FULLY }
    - { path: ^/api/.*, role: ROLE_REPRESENTATIVE }
    - { path: ^/admin/.*, role: ROLE_ADMIN }

    Any advice? What could be wrong here?

  • 2015-07-29 weaverryan

    You've probably seen my reply :). This has nothing to do with any authorization stuff. Instead, we simply need to allow this request to get through the firewall/authenticator anonymously. And yes, you *may* be able to use a route instead of a URL in getCredentials() - it would be if ($request->attributes->get('_route') == 'name_of_post_session_route') - that may or may not work, I can't remember the exact ordering of how routing runs (but I think it will work).

    Cheers!

  • 2015-07-29 weaverryan

    I would leave it in the pattern, but add this in getCredentials()


    public function getCredentials(Request $request)
    {
    if ($request->getPathInfo() == '/api/v1/sessions') {
    // skip authentication for this one endpoint
    return;
    }
    }

    Also make sure you have the "anonymous: ~" under your api_area firewall, to allow for anonymous requests.

    Cheers!

  • 2015-07-29 Reynier Pérez Mira

    That is exactly what I've asked minutes ago, how do I get postSessionAction() out from the pattern, can be achieved using Expression-based Authorization Language from JMSSecurityExtraBundle as shown here http://jmsyst.com/bundles/J... by adding isAuthenticated() as an annotation for that method? Or what you mean with "add an if statement in getCredentials and return null if the URL is to the postSessionAction endpoint"? Can I use routes instead of full Url?

  • 2015-07-29 Reynier Pérez Mira

    One last question, how do I get out /api/v1/sessions from the pattern? that method is the one that creates a valid repTokenId so in that case I can't check for valid credentials, any advice?

  • 2015-07-29 weaverryan

    Hey Reynier!

    Yep, I'm definitely cool with you posting a blog post - I think it would be awesome :). Also, the findOneBy(array()) call does *not* throw an exception itself: it either returns a Representative object or null. But yes, you can definitely raise/throw a AuthenticationCredentialsNotFoundException in getCredentials() - that's a good way of doing it. You'll just need to make sure that your postSessionAction() endpoint is *not* under /api, otherwise people will get this "Authentication credentials could not be found" when they try to get the token. Alternatively, if this *is* under /api, then you could add an if statement in getCredentials and return null if the URL is to the postSessionAction endpoint. Basically, you *do* want to allow anonymous users to this endpoint.

    Good luck!

  • 2015-07-29 Reynier Pérez Mira

    Ok, many thanks, apparently is working now, I am raising this AuthenticationCredentialsNotFoundException "{"message":"Authentication credentials could not be found."}" but I think it makes sense since I am forcing a non existent `repTokenId` so this line `$user = $this->em->getRepository('PDOneBundle:Representative')->findOneBy(array('repTokenId' => $credentials))` will throw the exception because there is not user with that value. I will continue testing this stuff and will let you know if I had any new issue in the near future, great support. Lets be in touch for translation and as I said I will like to write a small post on my blog talking about my experience and adding this things related to security and so on. Are you ok with that?

  • 2015-07-29 weaverryan

    Hi Reynier!

    Yes, this helps - I see the problem immediately - and it *is* indeed in security.yml as you thought. On each request, only *one* firewall is used, and the "pattern" key - which is a regular expression - is used to figure out which one firewall should be used. So right now, if I got to /api/foo, then Symfony looks at your main firewall, sees that it's pattern is ^/ (which matches everything) and selects *this* as the *one* firewall for this request. This means that anything under /api will use form_login - your api_area and the authenticator under it are never used.

    Simple fix:

    1) Add pattern: ^/api to your api_area firewall
    2) Move your api_area firewall *above* the admin_area firewall. Then, /api/foo will match api_area, and all other non /api URLs will match the admin_area firewall.

    When you do this, your var_dump and die() statements should be executed, and the entire process will probably start making more sense :).

    Cheers!

  • 2015-07-28 Reynier Pérez Mira

    Nothing happen, not `var_dump()` is executed at all neither the `die()` so I think is not being used. Sadly I can't share the whole project is not public and copyright doesn't allow me to share, but let me know what you need in order to help me. This is the config.yml (where I define the service): https://gist.github.com/paq... and this is the security.yml file: https://gist.github.com/paq... , perhaps this is where the problem is and I am not seeing. I've re-read the docs several times, is not the first time I got headaches with authentication/authorization flow but have a few gaps yet. Notice I have changed this `{ path: ^/api/.*, role: ROLE_REPRESENTATIVE }` line since I have a ROLE_REPRESENTATIVE defined on security.yml so no need to add a ROLE_API_REP or so. I have changed also at getRoles() in Representative entity to be the same in both places.

    Cheers

  • 2015-07-28 weaverryan

    Hey Reynier!

    If you put a var_dump($request);die; inside of your authenticator's getCredentials() method, does that execute? I want to make sure that your authenticator is configured correctly and is being used. I think that you're denying access with access_control (which is correct) but something is wrong with your authenticator. Either you are never being authenticated, or getRoles() is not returning ROLE_API_REP, so it looks like you do not have access.

    Also, I recommend re-reading my documentation about Guard, and maybe also the introduction to Symfony's security chapter. You have a lot of pieces, but I don't think you totally understand how the "flow" of authentication works :). All authentication happens *before* your controller is executed. All you do in your controller (or access_control) is "authorization": i.e. you're checking to see if the current user (who may or may not be anonymous) has a role. And btw, checking for IS_AUTHENTICATED_ANONYMOUSLY actually does nothing - that always returns true - see the description here: http://symfony.com/doc/curr...

    If you're able to post your entire setup somewhere on GitHub (i.e. a working project) - I can be even more helpful :).

    Cheers!

  • 2015-07-28 Reynier Pérez Mira

    I'm still having two more issues here, regarding `security.yml`:


    access_control:
    - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/api, role: ROLE_API_REP }
    - { path: ^/admin/.*, role: ROLE_ADMIN }

    I should allow IS_AUTHENTICATED_ANONYMOUSLY at this method https://gist.github.com/paq... since is the one that authenticate reps, how I achieve that?

    I'm getting an 403 forbidden exception even by sending `X-PDONE-SESSION-ID` as a request header, why?


    "error": {
    "code": 403,
    "message": "Forbidden",
    "message": "You do not have the necessary permissions",
    "class": "Symfony\\\\Component\\\\HttpKernel\\\\Exception\\\\AccessDeniedHttpException",
    }
  • 2015-07-28 weaverryan

    Hey Reynier!

    You're very close :). Adding the following changes will probably get what you want:

    1) In Representative, change getRoles():


    public function getRoles()
    {
    return array('ROLE_API_REP');
    }

    2) In security.yml, restrict all calls under `/api` to require this role:


    security:
    # ...
    access_control:
    - { path: ^/api, roles: ROLE_API_REP }

    The problem is simply that if you pass NO `X-PDONE-SESSION-ID`, then the authenticator does not deny access. Instead - because getCredentials() returns null - it allows the request to continue to your controller. BUT, that request is anonymous - it is not authenticated. So, as long as you have some code - either in access_control as shown here, or by calling isGranted('ROLE_API_REP') in your controller - that requires authentication, then this anonymous user will be denied access. Check out the 3 possibilities for getCredentials() in the table: https://knpuniversity.com/s...

    Does that help?

    Cheers!

  • 2015-07-28 Reynier Pérez Mira

    Hi @weaverryan, I think this is not working as should be. Why? Because I can call each method on the API and if I understood the purpose of the article that can't be allowed. Perhaps I am mistaken. What I am trying to do is authenticate the user (reps) before allow them to make any call to API methods. Right now I can call "/api/v1/reps/activity?_format=json&rid=1" without generate, before, a X-PDONE-SESSION-ID and send it through headers on the request, so, what is wrong here?

  • 2015-07-28 weaverryan

    Yep, the entity looks ok to me :).

    About "how do I check for valid token in each API call": You will *not* do this in your controller. The authenticator does this, and your authenticator is called before the controller is executed on every single request. So, by the time your controller is called, your user will already be authenticated or authentication will have already failed, and your controller won't be called. To find out which user (Representative in your case) is authenticated, you'll use the normal: $rep = $this->getUser(); in your controller.

    Cheers!

  • 2015-07-27 Reynier Pérez Mira

    Ok, I think I got this, can you take a look to the entity I just fixed and tell me if that looks good to you and is enough to get Authentication via Api Token done? Here is the link https://gist.github.com/paq.... In the other side how do I check for valid token in each API call? By using this piece of code:


    if ($request->headers->get('X-PDONE-SESSION-ID') === '' || $request->headers->get('X-PDONE-SESSION-ID') === null || $request->headers->get('X-PDONE-SESSION-ID') !== $this->container->get('session')->get('X-PDONE-SESSION-ID')) {

    $view->setData(array('error' => 'missing or invalid session id'))->setStatusCode(400);
    return $view;
    }


    Or how that part works?

  • 2015-07-27 weaverryan

    Yes, whatever you return from getUser() must implement UserInterface - that's a rule of Symfony's security, not just Guard. For any methods that don't really make sense - like getPassword() - just return `null`. The only one that should be important for you is getRoles() - make sure this returns at least *one* role - like ROLE_USER, or even ROLE_API_REP - whatever you want. Most of the other methods - like getPassword() - are important if you're letting Symfony handle a lot more of the authentication for you. You might also return the Rep's id for getUsername(), but that really shouldn't matter.

    Cheers!

  • 2015-07-27 Reynier Pérez Mira

    When you said this " you have a "Representative" instead of a User - but that's no different, as long as your Representative implements UserInterface", Representative is my entity should it implements UserInterface? In that case I would add the following methods to the entity: getRoles(), getPassword(), getSalt(), getUsername(), eraseCredentials(), in that case what them should return? or declare the entity class as abstract, is this fine? This is an advanced topic for me so I am confused. Take a look to this very basic flow - http://imgur.com/jmatcEe - of how things happen on the application and then based on that see if what we're talking is just fine

  • 2015-07-27 weaverryan

    Hi again!

    Ok, I think we understand each other quite well now :). It seems to me, then, that your situation is very close to the situation in this tutorial, except that you have a "Representative" instead of a User - but that's no different, as long as your Representative implements UserInterface :).

    You posted this gist: https://gist.github.com/paq.... I think this - specifically getCredentials() and getUser() - is perfect :). The ApiTokenAuthenticator::getCredentials() is called on EVERY request. And as long as it returns something (a non-null value), then getUser() is also called on EVERY request. This means that your ApiTokenAuthenticator should already do what you need: it checks for a valid X-PDONE-SESSION-ID on every request. If the client sends a *bad* X-PDONE-SESSION-ID, then your getUser() will fail and they will fail authentication. If the client does *not* send an X-PDONE-SESSION-ID, then they will not fail authentication, but they will continue into the system as an anonymous user. As long as you are requiring a valid user on all endpoints (e.g. via access_control or calling isGranted() in your controllers), then those requests will eventually be denied access (this is when the start()) method is called.

    Is that clear?

    And yes, we have wanted the have posts in Spanish for a long time. Y también, puedo hablar/escribir en español, pero no con la calidad de un hispanohablante verdadero :). Nos gustaría tener las traducciones publicados aquí en KnpUniversity, con crédito al traductor. Si tiene interés, me puede mandar un email - ryan@knpunversity.com.

    Cheers!

  • 2015-07-25 Reynier Pérez Mira

    Ok, sorry for confuse you with my code, still in development and need a lot of improvement and I am not an expert as you can see ;) anyway, you're on the right path: a User (lets call it Reps from now on since this is not a system - meaning admin or any kind of FOSUser user) login from iOs app against Salesforce and return backs to API the username (the one it uses to login), the veeva token and the instance URL. In the beginning I created a session for check the existence and validity of X-PDONE-SESSION-ID but now using Guard I think I got this covered. Each subsequent API call should check for valid X-PDONE-SESSION-ID value on the headers, how? Again, you tried to explain me but I not fully understand how to change my code to move to Guard and use Token authentication. Forget about session usage, I can get ride of it, can you provide me with a little set of classes and very basic flow as you did on main post for achieve this? (PS: Also I will like to translate and|or publish this post and other I've found on my site, can I? of course all the credits will goes to you and KnpLabs I am just bringing this to spanish community)

  • 2015-07-25 weaverryan

    Hi again!

    I'm afraid I don't really understand the setup. It looks like this is the goal of your setup:

    A) Allow the user to hit postSessionAction() with a username+veeva token. This is verified against SalesForce and a "representative id" is sent back (returned as a X-PDONE-SESSION-ID response header)

    B) On future requests, the user simply sends this X-PDONE-SESSION-ID as a request header. You'll use it to authenticate the user

    My big question:

    I don't understand at all why you're setting Session data in postSessionAction(). Usually, API's are stateless/sessionless. This means that if I make a request to postSessionAction(), my next request to some other API endpoint will not include a session cookie, and so therefore I'll have a completely new, fresh session. So, what's going on here?

    You have a setup that is *almost* what I'd expect, except for all the session-setting stuff. I don't think you need this. I'm guessing that once the user is authenticated, you'll need to know what "Representative" they are. If so, that is your "User", and you should return it from getUser() (which you are in fact doing). Just make sure that this implements UserInterface. Some methods like getPassword() won't make sense, so just return null - it's no problem.

    Cheers!

  • 2015-07-25 Reynier Pérez Mira

    Hi there @weaverryan, I think I get a little bit confused before you try to give me such good explanation. Let me show you what I do in my postSessionAction() method which is the first method I call to create the X-PDONE-SESSION-ID that I will like to use along the current session to allow valid API calls (authenticate ones). This (https://gist.github.com/paq... is the method and this is how ApiTokenAuthenticator looks (https://gist.github.com/paq..., perhaps I am not following you at all but I will be very graceful if you take a look to this piece of code and give me another answer perhaps with some example code to start on.

  • 2015-07-25 weaverryan

    Hey Reynier!

    Ultimately, when a user logs in, a User object will be created by you to represent that user. But this User object can be anything (as long as it implements UserInterface). And also, the "user provider" stuff system is technically optional. For example, you'll notice in this tutorial that I have a user provider configured in security.yml, but ultimately, even though that object is passed to getUser() in my authenticator, I don't use it at all. I believe that a user provider is only important if you're using session-based authentication. Specifically, at the beginning of each request, the User object (technically the token) is deserialized from the session, and UserProviderInterface::refreshUser() is called, in case you want to re-query the database to make sure the user info is up to date (but really, you could just return the User).

    So, this is so far just a mixture of facts. I'm not sure of your exact situation, but here's an idea of what you might do:

    A) Don't worry about the user provider at all. You already have one configured for FOSUserBundle, so let that one be used, but we won't use it for the API authentication.

    B) In your API authenticator, do whatever you need to do to authenticate the user. If these users aren't system users (and so it doesn't really make sense to query the User table for them - I believe this is your case) - then just create some User class that implements UserInterface, put whatever data/methods that you find useful in it, and create a new User() and return it from getUser().

    You may hit some issues, but the point is this: forget about using the user provider and create your own non-persisted User class if you want to. If you hit issues - let me know.

    Cheers!

  • 2015-07-25 Reynier Pérez Mira

    Nice and excellent tutorial, I have a slightly different situation and would like to know and advice from yours guys. I have two types of users on my application: system users that uses FOSUserBundle and API users that login against Salesforce a cURL call from iOs app sending username+password and sending back to Restful API a token, a URL and a username, all that I need to continue from there (I don't need to know and|or handle users passwords at all) and then set a repApiToken in it's table. That last ones are not system users and only needs validate against Salesforce and therefore at any API call. I would like to know how you will handle authenticate via API token for users that hasn't any providers at all because they don't need. Can you give me some advice on this topic? I've started coding based on this tutorial but stopped because of this.