How to Authenticate via an API Token

Suppose you want to have an API where users authenticate by sending an X-TOKEN header. With Guard, this is one of the easiest things you can setup.

For this example, we have a User entity that as an $apiToken property that's auto-generated for every user when they register:

135 lines src/AppBundle/Entity/User.php
... lines 1 - 19
class User implements UserInterface
{
... lines 22 - 48
/**
* @ORM\Column(type="string", unique=true)
*/
private $apiToken;
... lines 53 - 133
}

But your setup can look however you want: an independent ApiToken entity that relates to your User, no User entity at all, or api tokens that are validated in some other way entirely.

Installing Guard

Read the short Installation chapter to make sure you've got the bundle installed and enabled.

Creating an Authenticator

In Guard, the whole authentication process - reading the X-TOKEN header value, validating it, returning an error response, etc - is handled in a single class called an "Authenticator". Your authenticator can be as crazy as you want, as long as it implements KnpU\Guard\GuardAuthenticatorInterface.

Most of the time, you can extend a convenience class called AbstractGuardAuthenticator. Create a new ApiTokenAuthenticator class, make it extend this class, and add all the necessary methods:

49 lines src/AppBundle/Security/ApiTokenAuthenticator.php
... lines 1 - 2
namespace AppBundle\Security;
use KnpU\Guard\AbstractGuardAuthenticator;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
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;
class ApiTokenAuthenticator extends AbstractGuardAuthenticator
{
public function getCredentials(Request $request)
{
// TODO: Implement getCredentials() method.
}
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.
}
public function start(Request $request, AuthenticationException $authException = null)
{
// TODO: Implement start() method.
}
}

Your mission: fill in each method. We'll get to that in a second.

But to fill on those methods, we'll need to query the database. Let's pass the Doctrine EntityManager into our authenticator:

78 lines src/AppBundle/Security/ApiTokenAuthenticator.php
... lines 1 - 4
use Doctrine\ORM\EntityManager;
... lines 6 - 15
class ApiTokenAuthenticator extends AbstractGuardAuthenticator
{
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
... lines 24 - 76
}

Registering your Authenticator

Before filling in the methods, let's tell Symfony about our fancy new authenticator. First, register it as a service:

14 lines app/config/services.yml
... lines 1 - 5
services:
... lines 7 - 10
app.api_token_authenticator:
class: AppBundle\Security\ApiTokenAuthenticator
arguments: ["@doctrine.orm.entity_manager"]

Next, update your security.yml file to use the new service:

27 lines app/config/security.yml
... lines 1 - 18
main:
... line 20
knpu_guard:
authenticators:
- app.form_login_authenticator
- app.api_token_authenticator
# by default, use the start() function from FormLoginAuthenticator
entry_point: app.form_login_authenticator

Your firewall (called main here) can look however you want, as long as it has a knpu_guard section under it with an authenticators key that includes the service name that you setup a second ago (app.api_token_authenticator in my example).

Tip

The other authenticator - app.form_login_authenticator - is for my login form. If you don't need to also allow users to login via a form, then you can remove this. The entry_point option is only needed if you have multiple authenticators. See How can I use Multiple Authenticators?.

Filling in the Authenticator Methods

Your authenticator is now being used by Symfony. So let's fill in each method:

getCredentials()

78 lines src/AppBundle/Security/ApiTokenAuthenticator.php
... lines 1 - 7
use Symfony\Component\HttpFoundation\Request;
... lines 9 - 15
class ApiTokenAuthenticator extends AbstractGuardAuthenticator
{
... lines 18 - 24
public function getCredentials(Request $request)
{
return $request->headers->get('X-TOKEN');
}
... lines 29 - 76
}

The getCredentials() method is called on every single request and its job is to fetch the API token and return it.

Well, that's pretty simple. From here, there are 3 possibilities:

# Conditions Result Next Step
A) Return non-null value Authentication continues getUser()
B) Return null + endpoint requires auth Auth skipped, 401 response start()
C) Return null+ endpoint does not require auth Auth skipped, user is anon nothing

A) The X-TOKEN header exists, so this returns a non-null value. In this case, getUser() is called next.

B) The X-TOKEN header is missing, so this returns null. But, your application does require authentication (e.g. via access_control or an isGranted() call). In this case, see start().

C) The X-TOKEN header is missing, so this returns null. But the user is accessing an endpoint that does not require authentication. In this case, the request continues anonymously - no other methods are called on the authenticator.

getUser()

If getCredentials() returns a non-null value, then getUser() is called next. Its job is simple: return a the user (an object implementing UserInterface):

78 lines src/AppBundle/Security/ApiTokenAuthenticator.php
... lines 1 - 10
use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException;
... lines 12 - 13
use Symfony\Component\Security\Core\User\UserProviderInterface;
... line 15
class ApiTokenAuthenticator extends AbstractGuardAuthenticator
{
... lines 18 - 29
public function getUser($credentials, UserProviderInterface $userProvider)
{
$user = $this->em->getRepository('AppBundle:User')
->findOneBy(array('apiToken' => $credentials));
// we could just return null, but this allows us to control the message a bit more
if (!$user) {
throw new AuthenticationCredentialsNotFoundException();
}
return $user;
}
... lines 42 - 76
}

The $credentials argument is whatever you returned from getCredentials(), and the $userProvider is whatever you've configured in security.yml under the providers key.

You can choose to use your provider, or you can do something else entirely to load the user. In this case, we're doing a simple query on the User entity to see which User (if any) has this apiToken value.

From here, there are 2 possibilities:

# Conditions Result Next Step
A) Return a User Authentication continues checkCredentials()
B) Return null or throw AuthenticationException Authentication fails onAuthenticationFailure()

A) If you successfully return a User object, then, checkCredentials() is called next.

B) If you return null or throw any Symfony\Component\Security\Core\Exception\AuthenticationException, authentication will fail and onAuthenticationFailure() is called next.

checkCredentials()

If you return a user from getUser(), then checkCredentials() is called next. Here, you can do any additional checks for the validity of the token - or anything else you can think of. In this example, we're doing nothing:

80 lines src/AppBundle/Security/ApiTokenAuthenticator.php
... lines 1 - 12
use Symfony\Component\Security\Core\User\UserInterface;
... lines 14 - 15
class ApiTokenAuthenticator extends AbstractGuardAuthenticator
{
... lines 18 - 42
public function checkCredentials($credentials, UserInterface $user)
{
// the fact that they had a valid token that *was* attached to a user
// means that their credentials are correct. So, there's nothing
// additional (like a password) to check here.
return;
}
... lines 50 - 78
}

Like before, $credentials is whatever you returned from getCredentials(). And now, the $user argument is what you just returned from getUser().

From here, there are 2 possibilities:

# Conditions Result Next Step
A) do anything except throwing an AuthenticationException Authentication successful onAuthenticationSuccess()
B) Throw any type of AuthenticationException Authentication fails onAuthenticationFailure()

A) If you don't throw an exception, congrats! You're authenticated! In this case, onAuthenticationSuccess() is called next.

B) If you perform extra checks and throw any Symfony\Component\Security\Core\Exception\AuthenticationException, authentication will fail and onAuthenticationFailure() is called next.

onAuthenticationSuccess

Your user is authenticated! Amazing! At this point, in an API, you usually want to simply let the request continue like normal:

78 lines src/AppBundle/Security/ApiTokenAuthenticator.php
... lines 1 - 7
use Symfony\Component\HttpFoundation\Request;
... line 9
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
... lines 11 - 15
class ApiTokenAuthenticator extends AbstractGuardAuthenticator
{
... lines 18 - 57
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
// do nothing - let the request just continue!
return;
}
... lines 63 - 76
}

If you return null from this method: the request continues to process through Symfony like normal (only now, the request is authenticated).

Alternatively, you could return a Response object here. If you did, that Response would be returned to the client directly, without executing the controller for this request. For an API, that's probably not what you want.

onAuthenticationFailure

If you return null from getUser() or throw any AuthenticationException from getUser() or checkCredentials(), then you'll end up here. Your job is to create a Response that should be sent back to the user to tell them what went wrong:

78 lines src/AppBundle/Security/ApiTokenAuthenticator.php
... lines 1 - 6
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
... lines 9 - 11
use Symfony\Component\Security\Core\Exception\AuthenticationException;
... lines 13 - 15
class ApiTokenAuthenticator extends AbstractGuardAuthenticator
{
... lines 18 - 48
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
return new JsonResponse(
// you could translate the message
array('message' => $exception->getMessageKey()),
403
);
}
... lines 57 - 76
}

In this case, we'll return a 403 Forbidden JSON response with a message about what went wrong. The $exception argument is the actual AuthenticationException that was thrown. It has a getMessageKey() method that contains a safe message about the authentication problem.

This Response will be sent back to the client - the controller will never be executed for this request.

start()

This method is called if an anomymous user accesses en endpoint that requires authentication. For our example, this would happen if the X-TOKEN header is empty (and so getCredentials()) returns null. Our job here is to return a Response that instructs the user that they need to re-send the request with authentication information (i.e. the X-TOKEN header):

78 lines src/AppBundle/Security/ApiTokenAuthenticator.php
... lines 1 - 7
use Symfony\Component\HttpFoundation\Request;
... lines 9 - 11
use Symfony\Component\Security\Core\Exception\AuthenticationException;
... lines 13 - 68
public function start(Request $request, AuthenticationException $authException = null)
{
return new JsonResponse(
// you could translate the message
array('message' => 'Authentication required'),
401
);
}
}

In this case, we'll return a 401 Unauthorized JSON response.

supportsRememberMe

This method is required for all authenticators. If true, then the authenticator will work together with the remember_me functionality on your firewall, if you have it configured. Obviously, for an API, we don't need remember me functionality:

78 lines src/AppBundle/Security/ApiTokenAuthenticator.php
... lines 1 - 15
class ApiTokenAuthenticator extends AbstractGuardAuthenticator
{
... lines 18 - 63
public function supportsRememberMe()
{
return false;
}
... lines 68 - 76
}

Testing your Endpoint

Yes! API token authentication is all setup. Let's test it with a simple script.

First, install Guzzle:

composer require guzzlehttp/guzzle:~6.0

Next, create a little "play" file at the root of your project that will make a request to our app. This assume your web server is running on localhost:8000:

18 lines testAuth.php
... lines 1 - 2
require __DIR__.'/vendor/autoload.php';
$client = new GuzzleHttp\Client();
$res = $client->get('http://localhost:8000/secure', [
'allow_redirects' => false,
'http_errors' => false,
'headers' => [
// token for anna_admin in LoadUserData fixtures
'X-Token' => 'ABCD1234'
]
]);
echo sprintf("Status Code: %s\n\n", $res->getStatusCode());
echo $res->getBody();
echo "\n\n";

In our app, the anna_admin user in the database has an apiToken of ABCD1234. In other words, this should work. Try it out from the command line:

php testAuth.php

If you see a 200 status code with response of "It works!"... well, um... it works! The /secure controller requires authentication. Now try changing the token value (or removing it entirely) to see our error responses.

Leave a comment!

  • 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/ircmaxell/R.... 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/quest... 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/knpuniversi....

    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/quest... 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/scre... 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/JMSS... 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/paquit... and this is the security.yml file: https://gist.github.com/paquit... , 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/current...

    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/paquit... 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/scre...

    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/paquit.... 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/paquit.... 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/paquit... is the method and this is how ApiTokenAuthenticator looks (https://gist.github.com/paquit..., 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.