If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
So we can deny access and turn that into a nice response. Cool. Now we need to create something that’ll look for a token on the request and authenticate us so we can actually create programmers!
You should know where to start: in one of our feature files. We’re going to modify an existing scenario. See the Background of our programmer feature file? One of the things that we do before every single scenario is to make sure the weaverryan user exists in the database. We aren’t sending authentication headers, just guaranteeing that the user exists in the database:
# features/api/programmer.feature
# ...
Background:
Given the user "weaverryan" exists
# ... scenarios
In the background, I already have a database table for tokens, and each token has a foreign-key relation to one user. So I’m going to extend the Background a little bit to create a token in that table that relates to weaverryan. And this is the important part, this says that on whatever request we make inside of our scenario, I want to send an Authorization header set to token, a space then ABCD123:
# features/api/programmer.feature
# ...
Background:
Given the user "weaverryan" exists
And "weaverryan" has an authentication token "ABCD123"
And I set the "Authorization" header to be "token ABCD123"
# ... scenarios
Why did I choose to set the Authorization header or this “token space” format? Technically, none of this is important. In a second, you’ll see us grab and parse this header. If you use OAuth, it has directions on the type of names you should give these things. So I’m just using authorization header and the word token, space and the actual authentication token that we’re sending.
By the way, we aren’t doing it in this tutorial, but one thing that that’s really important for authentication across your API is that you only do it over HTTPS. The easiest way to do this is to require HTTPS across your entire API. Otherwise, these authentication tokens are flying through the internet via plain text, and that’s a recipe for disaster.
If we rerun one of our tests right now, it’s not going to make any difference. To prove it, let’s rerun the first scenario of programmer.feature, which starts at line 11. So we say :11 and it’s going to fail:
php vendor/bin/behat features/api/programmer.feature:11
It is setting that Authorization header, but we aren’t actually doing anything with it yet in our app. So we’re getting that 401 authentication required message.
So let’s hook this up! Some of this is specific to Silex’s security system, but in case you’re using something else, we’ll stay high level enough to see what types of things you need to do in your system to make it happen. As always, if you have questions, just ask them in the comments!
Inside this Security/ directory here, I’ve already set up a bunch of things for an API token authentication system
The first thing we’re going to do is open this ApiTokenListener. I’ve written some fake code in here as you can see:
// src/KnpU/CodeBattle/Security/Authentication/ApiTokenListener.php
// ...
class ApiTokenListener implements ListenerInterface
{
// ...
public function handle(GetResponseEvent $event)
{
// ...
$request = $event->getRequest();
// there may not be authentication information on this request
if (!$request->headers->has('Authorization')) {
return;
}
// TODO - remove this return statement and add real code!
return;
// format should be "Authorization: token ABCDEFG"
$tokenString = 'HARDCODED';
if (!$tokenString) {
// there's no authentication info for us to process
return;
}
// some code that sends the tokenString into the Silex security system
// ...
}
// ...
}
The job of the listener is to look at the request object and get the token information off of it. And hey, since we’re sending the token on the Authorization header, we are going to look for it there. So let’s get rid of this hard coded text and instead go get that Authorization header. You can say $request->headers->get('Authorization'). That’s going to get you the actual raw token ABCD123 type of thing:
// src/KnpU/CodeBattle/Security/Authentication/ApiTokenListener.php
// ...
public function handle(GetResponseEvent $event)
{
// ...
$request = $event->getRequest();
$authorizationHeader = $request->headers->get('Authorization');
// ...
}
Next, since the actual token is the second part of that, we need to parse it out. I’ll say, $tokenString = $this->parseAuthorizationHeader(), which is a function I’ve already created down here. It’s a private function that expects a format of token space and gets the second part for you:
// src/KnpU/CodeBattle/Security/Authentication/ApiTokenListener.php
// ...
public function handle(GetResponseEvent $event)
{
// ...
$request = $event->getRequest();
$authorizationHeader = $request->headers->get('Authorization');
$tokenString = $this->parseAuthorizationHeader($authorizationHeader);
// ...
}
/**
* Takes in "token ABCDEFG" and returns "ABCDEFG"
*/
private function parseAuthorizationHeader($authorizationHeader)
{
// ...
}
Perfect!
At this point the $tokenString is ABCD123. So that’s all I want to talk about in this TokenListener, it’s the only job of this class.
Next, I’m going to open up the ApiTokenProvider. Its job is to take the token string ABCD123 and try to look up a valid User object in the database for it. First, remember how I have an api_token table in my database? Let me show you some of the behind-the-scenes magic:
// src/KnpU/CodeBattle/DataFixtures/FixturesManager.php
// this is an internal class that creates our database tables
$tokenTable = new Table('api_token');
$tokenTable->addColumn('id', 'integer'();
$tokenTable->addColumn('token', 'string', array('length' => 32));
$tokenTable->addColumn('userId', 'integer');
$tokenTable->addColumn('notes', 'string', array('length' => 255));
$tokenTable->addColumn('createdAt', 'datetime');
// ...
You can see here I am creating an api_token table. It has a token column which is the string and a user_id column which is the user it relates to. So you can imagine a big table full of tokens and each token is related to exactly one user. For example, if we look up the entry in the token table, we can figure out “yes” this is a valid token and it is a valid token for a user whose id is 5.
So here, the first thing we’ll do is actually go and look up the token row. I don’t want to get into the details of exactly how this all hooks up because I want to focus on REST. But I’ve already configured this class and created some code behind the scenes to take in a token string, which is the ABCD123 thing in our case and return to me an ApiToken object, which represents a row in that table:
// src/KnpU/CodeBattle/Security/Authentication/ApiTokenProvider.php
// ...
class ApiTokenProvider implements AuthenticationProviderInterface
{
// ...
public function authenticate(TokenInterface $token)
{
// the actual token string value from the header - e.g. ABCDEFG
$tokenString = $token->getCredentials();
// find the ApiToken object in the database based on the TokenString
$apiToken = $this->apiTokenRepository->findOneByToken($tokenString);
if (!$apiToken) {
throw new BadCredentialsException('Invalid token');
}
// ... finishing code that's already written ...
}
// ...
}
So we’ve taken the string and we’ve queried for a row in the table. If we don’t have that row, we throw an exception which causes a 401 bad credentials error.
Next, when we have that, we just need to look up the User object from it. Remember, the job of this class is start with the token string and eventually give us a User object. And it does that by going through the api_token table:
// src/KnpU/CodeBattle/Security/Authentication/ApiTokenProvider.php
// ...
class ApiTokenProvider implements AuthenticationProviderInterface
{
// ...
public function authenticate(TokenInterface $token)
{
// the actual token string value from the header - e.g. ABCDEFG
$tokenString = $token->getCredentials();
// find the ApiToken object in the database based on the TokenString
$apiToken = $this->apiTokenRepository->findOneByToken($tokenString);
if (!$apiToken) {
throw new BadCredentialsException('Invalid token');
}
$user = $this->userRepository->find($apiToken->userId);
// ... finishing code that's already written ...
}
// ...
}
And that’s the job of this ApiTokenProvider class. It’s technical and at the core of Silex’s security system, so I just want you to internalize what it does.
At this point - between these two classes and a few other things I’ve setup - if we send this Authorization header with a valid token, by the time we get it to our ProgrammerController, $this->getLoggedInUser() will actually return to us the User object that’s attached to the token that was sent:
// src/KnpU/CodeBattle/Controller/Api/ProgrammerController.php
// ...
public function newAction(Request $request)
{
// will return the User related to the token form the Authorization header!
if (!$this->isUserLoggedIn()) {
throw new AccessDeniedException();
}
// ...
}
In the case of our scenario, since we’re sending a token of ABCD123, it means that we’ll get a User object that represents this weaverryan user. We will actually be logged in, except we’re logged in via the API token. So, let’s try this out.
php vendor/bin/behat features/api/programmer.feature:11
And there it is!
The guts for getting this all working can be complicated, but the end result is so simple: send an Authorization header with the api token and use that to look in your database and figure out which User object if any this token is attached to.
So now, in handleRequest(), I have this ugly hard-coded logic that assumed that there is a user called weaverryan. Replace this garbage with $this->getLoggedInUser() to get the real user object that’s attached to our token:
// src/KnpU/CodeBattle/Controller/Api/ProgrammerController.php
// ...
private function handleRequest(Request $request, Programmer $programmer)
{
// ...
$programmer->userId = $this->getLoggedInUser()->id;
}
Nevermind. Was doing this via a Vagrant VM and that seemed to be the issue. Now just running localhost natively.
Same and it appeared to be the rewrite rules causing it. Had to add this to my rewrite conditions in my apache config:
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule .* - [e=HTTP_AUTHORIZATION:%1]
Ah, nice debugging! Sometimes, your web server can be configured to "hide" the Authorization header... though I'm not sure why! In case it helps: http://stackoverflow.com/qu...
Cheers!
Hey,
Really like your videos. They are awesome! But they would be even better if you could remove hissing that occurs when you say an "s". Other than that keep up the good work !! :)
well, I made it this far. I'm afraid I'll have to ask... sorry :P
So I brought all the Security part into my project and I am trying authentication for a specific request. If I don't send the authorization header I get the same error you got in the previous chapter, the API returns:
{
"detail": "Authentication Required",
"status": 401,
"type": "http:\/\/localhost:8000\/docs\/errors#authentication_error",
"title": "Invalid or missing authentication"}
which is alright.
However, now I am trying to get the request to execute properly, by adding an Authorization header with the content "token ABCD123" using Postman. I have defined the ApiToken table with the same fields (I just changed userId for user_id in the entire project for consistency with my other classes) and of course added a row with ABCD123 and user_id pointing to the user who should've access.
At this point, I keep getting "Authentication Required". My first question would be, how can I debug from here? I cannot use var_dumps because the api will just return the response. I don't even know if the ApiTokenListener is executing at all. I guess I broke something with the Silex Security. If I get Authentication Required it means that either ApiTokenListener was not executed, or that there was no authentication information on the request (which would mean postman fails) or there's no match in the database (there is!!)... I need to get closer to the problem but I'm unsure on how to debug this with postman alone. How would you proceed? Thanks!
Hi Joan!
Don't worry about asking - it's kind of what I hoped for ;). We don't talk about the security stuff in great detail because it's complicated and implementing it isn't directly related to REST. Anyways, let me see if I can help :).
First, the ApiTokenListener should be called on *every* request to your app. Add a die('fooo'); in the handle() method and make a request to your app. Is this method being called? If you don't see "foo" and just see the response, then this method is *not* being called. You *should* be able to use var_dump (with die) to debug things. If the method is not being called. the first thing to check is that this is all configured in the Application.php file - specifically, there should be a 'api_token' => true, line (not commented) under the "api" firewall in the configureSecurity method. In theory, if that line is there, then your security listener should be called.
You can also take a look at the "finish" code in the code download and see if you spot any differences related to security.
Let me know how it goes!
hey Ryan! thanks for the prompt response, I was about to edit my question hehe, I managed to narrow it down to the fact that apparently Postman isn't setting the Authorization header.
I have:
$request = $event->getRequest();
var_dump($request->headers->has('Authorization'));
// there may not be authentication information on this request
if (!$request->headers->has('Authorization')) {
return;
}
in the handle(), the var_dump returns false. So yeah the security listener is being called but enters in that if returning nothing, and I guess that makes getLoggedInUser to return false and the whole request to return "auth required". Now I am trying to find out what could I be doing wrong, because Postman is relatively simple here, I mean I set the Authorization header to "token ABCD123"... also printing the whole $request crashes the app. Kinda stuck right now but don't really have a question this time :P
apparently it's a symfony/apache issue removing the header: https://github.com/dingo/ap... will update if I make it through
Yep, sorry for spamming here, if someone has issues with not getting the header you should add this to your .htaccess:
RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
Ah, so happy you found it, but that's no fun! And yes, now that I'm looking into this, it looks like a well-known issue. We even have some docs about it for Symfony: http://symfony.com/doc/curr... (scroll near the bottom of this section).
Thanks for posting your solution and now you can keep moving :)
Adding that RewriteRule to the .htaccess works at the time to pass the Authorization header through. However, now other requests that do not require authentication do get an empty Authorization header that causes the code to throw new BadAuthHeaderFormatException();.
I have modified it the code to look for empty authorization headers too:
// there may not be authentication information on this request
if (!$request->headers->has('Authorization') || $request->headers->get('Authorization') == '') {
return;
}
EDIT:
Added the following condition in the .htaccess:
RewriteCond %{HTTP:Authorization} .+
Hey Daan Biesterbos
Since you are the first one saying it, I would say, yes :)
But, could you tell us specifically what's bothering you?
Cheers!
Heheh. Well I made it sound more serious than intended I suppose xD I would totally complain in a PR when someone would mix camel case and snake case in the same context ^_^
I'll just make a more conceptual question too: I am developing an API with the idea to be consumed by one js app. The API has some requests that only the app should have access to (and not the users logged in the app). Does it make sense to create an Api Token uniquely for the app itself? I guess that some logic should be done for Silex to give special app credentials to requests using that token and finally, the app would have access to execute certain requests. Or which would be the way to accomplish this?
Hey Joan!
Before I try to give some ideas, let me make a quick point :). It will never be possible to allow JavaScript to be able to execute an endpoint that your logged in users cannot execute. If you think about it, even if you gave your JS some token it could pass back, that token will be exposed to the client (since JS is obviously client-side), and your users could always "steal" that and make those requests directly. And if you *stop* caring about trying to make your JS be able to do something that your logged in user cannot, then you *might* arrive at a very easy solution where you don't use any fancy API token stuff. Instead, you just let your JS use your user's session cookie and be done with it :).
Cheers!
One of the requests that made me ask this was the one about getting stats, which basically returns counts of the database for showing some numbers in the landing page, where noone is logged in. I guess that request can be public. However, what about a request to get all users? I realize that you don't have any UserController for your API and I am not sure if it is because you didn't need to do it for the purpose of this course or you didn't do it because you didn't want to. I have a UserController to create newUsers (when a user registers), to get all the users, etc but now I'm realizing that the app will only need, the POST to create a user, the GET to for a user to see its own info, the PUT in case a user wants to edit the email or password, and the DELETE in case a user wants to delete their account. So the GET all users shouldn't probably exist as it would be giving info of other users away. How do you deal with user registration via API?
// composer.json
{
"require": {
"silex/silex": "~1.0", // v1.3.2
"symfony/twig-bridge": "~2.1", // v2.7.3
"symfony/security": "~2.4", // v2.7.3
"doctrine/dbal": "^2.5.4", // v2.5.4
"monolog/monolog": "~1.7.0", // 1.7.0
"symfony/validator": "~2.4", // v2.7.3
"symfony/expression-language": "~2.4", // v2.7.3
"jms/serializer": "~0.16", // 0.16.0
"willdurand/hateoas": "~2.3" // v2.3.0
},
"require-dev": {
"behat/mink": "~1.5", // v1.5.0
"behat/mink-goutte-driver": "~1.0.9", // v1.0.9
"behat/mink-selenium2-driver": "~1.1.1", // v1.1.1
"behat/behat": "~2.5", // v2.5.5
"behat/mink-extension": "~1.2.0", // v1.2.0
"phpunit/phpunit": "~5.7.0", // 5.7.27
"guzzle/guzzle": "~3.7" // v3.9.3
}
}
Hi. The authorization test doesn't appear to be working anymore. I'm still not getting the authorization headers in the behat tests even after adding the suggested rewrite rules. The listener is called but authorization is not in the headers. Any ideas?