You again? Get outta here.... punk... is what we will be saying soon to API clients in this tutorial that don't have valid credentials! Yep, welcome back guys, this time to a tutorial that's making security exciting again! Seriously, I'm pumped to talk about authentication in an API... and in particular, a really powerful tool called JSON web tokens.

To make sure your JSON web tokens are the envy of all your friends, code along with me by downloading the code from any of the tutorial pages. Then, just unzip it and move into the start/ directory. I already have that start code in symfony-rest.

I also upgraded our project to Symfony 3! Woohoo! Almost everything we'll do will work for Symfony 2 or 3, but there are a few differences in the directory structure. We have a tutorial on upgrading to Symfony 3 if you want to see those.

Let's start the built-in web server with:

bin/console server:run

And if you just downloaded the code, open the README and follow a few other steps there.

The (sad) State of our App's Security

Ok, our app is Code Battles! It has a cool web interface and you can login with weaverryan and password foo: super secure! Here, we can create programmers and start battles. And our API already supports a lot of this stuff.

Open up ProgrammerController inside the Controller/Api directory:

191 lines src/AppBundle/Controller/Api/ProgrammerController.php
... lines 1 - 18
class ProgrammerController extends BaseController
... lines 21 - 24
public function newAction(Request $request)
... lines 26 - 54
public function showAction($nickname)
... lines 56 - 76
public function listAction(Request $request)
... lines 78 - 95
public function updateAction($nickname, Request $request)
... lines 97 - 128
public function deleteAction($nickname)
... lines 130 - 189

Awesome! We can already create, fetch and update programmers. AND, we've got a pretty sweet test:

248 lines tests/AppBundle/Controller/Api/ProgrammerControllerTest.php
... lines 1 - 6
class ProgrammerControllerTest extends ApiTestCase
... lines 9 - 15
public function testPOST()
... lines 17 - 36
public function testGETProgrammer()
... lines 38 - 59
public function testGETProgrammerDeep()
... lines 61 - 73
public function testGETProgrammersCollection()
... lines 75 - 91
public function testGETProgrammersCollectionPagination()
... lines 93 - 142
public function testPUTProgrammer()
... lines 144 - 164
public function testPATCHProgrammer()
... lines 166 - 183
public function testDELETEProgrammer()
... lines 185 - 246

um, suite... that checks these endpoints.

Ready for the problem? Our API has no security! The horror! Anonymous users are able to create programmers and then change the avatar on other programmers. It's chaos!

On the web interface, you need to be logged in to do any of these things. Let's make the API work the same way.

Testing for Security

As always: we need to start by writing a test. In ProgrammerControllerTest, add a new public function testRequiresAuthentication():

256 lines tests/AppBundle/Controller/Api/ProgrammerControllerTest.php
... lines 1 - 6
class ProgrammerControllerTest extends ApiTestCase
... lines 9 - 247
public function testRequiresAuthentication()
... lines 250 - 253

Let's make an API request to an endpoint that should be secured and then assert some things. Start with $response = $this->client->post('/api/programmers'). Send this a valid JSON body:

256 lines tests/AppBundle/Controller/Api/ProgrammerControllerTest.php
... lines 1 - 6
class ProgrammerControllerTest extends ApiTestCase
... lines 9 - 247
public function testRequiresAuthentication()
$response = $this->client->post('/api/programmers', [
'body' => '[]'
... line 253

Ok, if our API client tries to anonymously access a secured endpoint, what should be returned? Well, at the very least, assert that the response status code is 401, meaning "Unauthorized":

256 lines tests/AppBundle/Controller/Api/ProgrammerControllerTest.php
... lines 1 - 249
$response = $this->client->post('/api/programmers', [
'body' => '[]'
$this->assertEquals(401, $response->getStatusCode());
... lines 254 - 256

Ok! Let's go make sure this fails! Copy the method name and find the terminal. Run:

./vendor/bin/phpunit --filter testRequiresAuthentication

It fails with a validation error: it is getting beyond the security layer and executing our controller. Time to lock that down!

Securing a Controller

Open ProgrammerController. How can we require the API client to be authenticated? The exact same way you do in a web application. Add $this->denyAccessUnlessGranted('ROLE_USER'):

193 lines src/AppBundle/Controller/Api/ProgrammerController.php
... lines 1 - 18
class ProgrammerController extends BaseController
... lines 21 - 24
public function newAction(Request $request)
... lines 28 - 50
... lines 52 - 191

That's it. I'm using ROLE_USER because all of my users have this role - you could also use IS_AUTHENTICATED_FULLY.

Ok, back to the test! Run it!

./vendor/bin/phpunit --filter testRequiresAuthentication

Oh, interesting - it's a 200 status code instead of 401. Look closely: it redirected us to the login page. So, it's kind of working... you can't add programmers anonymously anymore. But clearly, we've got some work to do.

Leave a comment!

  • 2018-06-01 Diego Aguiar

    Hey Uns Rhandour

    How are you submitting the parameters to the API endpoint? In the video Ryan uses HTTP Basic Authentication. If you are sending them through the form body then you have to retrieve them from the Request in a different way.

    // in some Controller's action

    $data = json_decode($request->getContent(), true);
    // $data['form_field_name'] should be set

    That should do the trick. Cheers!

  • 2018-05-30 Uns Rhandour

    I'm using your application as API, and I built a react application as front-end.
    the issue is I am sending the credential to the API via JWT, but when I check the request object to look for the credential, I get null ($request->getUser()).

  • 2018-05-14 Diego Aguiar

    Hey Andrés Mardones
    That's weird. Can you double check the following:
    - Your test class extends PHPunit base class
    - Your test class ends with the word "Test" (SomeClassTest.php)
    - Test methods starts with the word "test" (testSomeMethod)


  • 2018-05-12 Andrés Mardones

    Got the same error! Running only ./vendor/bin/phpunit also says no tests executed.

  • 2018-01-16 Diego Aguiar

    Hey Howard Ng

    Can you execute any other test?
    This may sound silly but, could you double check that you actually have a test method called "testTokenControllerTest"

    If that's not the case, let us know!

  • 2018-01-14 Howard Ng

    No matter how I try edit file in /start or try /finish version. Both return:
    Anything I should do to set it up correctly?

    ./vendor/bin/phpunit --filter testTokenControllerTest
    PHPUnit 4.6.10 by Sebastian Bergmann and contributors.

    Configuration read from /var/www/symfony-rest4/finish/phpunit.xml.dist

    Time: 138 ms, Memory: 4.00Mb

    No tests executed!

  • 2017-05-30 Diego Aguiar

    Hey Karolos!

    Sorry for the late response, we apologize for any inconvenience you might have had

    The content type is "text/html" because you are been redirected to the login page after hitting "api/programmers" endpoint (because you don't have access to it, yet), that behaviour is a symfony's default when it detects an "AccessDeniedException" exception, and that's exactly what the method "denyAccessUnlessGranted" throws when the User doesn't own the specified ROLE

    The weird thing is why it says Not Found, instead of Login (below line HTML Summary (h1 and h2):), if you remove the security check in ProgrammerController::newAction() and hit again that endpoint, do you get the same response ?


  • 2017-05-30 karolos

    6 days ago paid engough

  • 2017-05-24 karolos

    Hi im trying to run the app but the test are not running .

    $ ./vendor/bin/phpunit --filter testRequiresAuthentication
    PHPUnit 4.6.10 by Sebastian Bergmann and contributors.


    Failure! when making the following request:
    POST: /api/programmers


    Content-Type: text/html; charset=iso-8859-1

    HTML Summary (h1 and h2):
    Not Found

    I dont understand why the response is always a tex/html I didnt changed anything I just downloaded the code and followed the instructions!

  • 2016-11-02 Victor Bocharsky

    Hey Ruslan,

    Actually, yes, the application uses another DB in test environment - you can see it in app/config/config_test.yml file.

    It depends, in most cases you will be enough to create a DB and create its schema, i.e. run "bin/console doctrine:database:create --env=test" and "bin/console doctrine:schema:create --env=test" commands at the first time, then you can just do "bin/console doctrine:schema:update --force --env=test" after any Doctrine mapping changes. But if your tests depend of some fixtures data - then you also need to load data fixtures too for test environment using the same "--env=test" option.


  • 2016-11-01 Ruslan

    I'm trying to run unit test on the end, but get an error:

    Caused by PDOException: SQLSTATE[HY000] [1049] Unknown database 'knp_api_token_test'

    My database name is knp_api_token, and it works correctly (I can log in with user credentials).

    However, if I create another database and call it 'knp_api_token_test' the above error disappears.

    So is it true, that for unit tests we use apart database which is a full clone of the dev database?

  • 2016-06-24 Mihail

    Thanks Ryan for advice! Just removing the type-hint 'Exeption' in onNotSuccessfulTest helped to work with this code! With ResponseAsserter methods also evth is ok, I just noticed the comment "this will blow up if the property doesn't exist" so if no problem the tests pass, but assertion of this type is just not added to the total number of the assertions in console info when tests were run, I just thought it didn't work, but they worked as expected on 'wrong' data. Thanks again, this weekend I am going to explore the fifth chapter :)

  • 2016-06-21 weaverryan

    Hey Mihail!

    Hmm - what problems are you having with each method?

    I *do* know that in more recent version of PHPUnit, that the argument to onNotSuccessfulTest changed (in newer versions, the type-hint was removed). You'll get a pretty clear error about the argument type mis-match in this case, which you can easily fix by removing the type-hint on the argument. But if there's a different issue, I haven't hit it yet. I haven't had any issues with assertResponsePropertiesExists/s() yet - and I *have* run this on Sf 3.0 and Guzzle 6.* (But not on PHP7).

    If there is a problem and we can solve it, we can commit the fix to the code download for others :).


  • 2016-06-20 Mihail

    Hi Ryan,

    After the complete rewriting of the project with SF3.1, Guzzle6.2, PHPUnit5.4, ResponseAsserter::assertResponsePropertiesExist/s() methods stopped working as expected as well as ApiTestCase::onNotSuccessfulTest with other debugging methods. For the latter, probably smth connected to PHP7 problem, but for the first it just passes without even considering it as an assertion.

    Could you advise on the way how to fix it?

  • 2016-06-10 weaverryan

    Haha, well done :)

  • 2016-06-10 Chuck Norris

    Hi Ryan,

    yes I upgraded to latest version and started upgrading following guzzle recommendations but I got stuck in how to handle history.
    So thanks to you, now I can get a closer look at what you did.

    P.S. No, unfortunately, I'm just usurping his identity, "cos you know, the real Chuck don't ask for help :)
    I just hope he never find out.

  • 2016-06-09 weaverryan

    Hey Chuck!

    Did you also upgrade Guzzle to the latest version (6)? I did for this tutorial (as you probably already know). And upgrading Guzzle actually took some work - specifically in ApiTestCase - several things changed. So, here's a copy of the ApiTestCase after the Guzzle upgrade: - I hope it's helpful :).


    P.S. Is your name really Chuck Norris? That would be awesome :)

  • 2016-06-09 Chuck Norris

    Hello KnpTeam,

    I'm trying to update the project to Symfony3 (and I don't have access to current project download, so I cant see how you do it) but I cant see how to import some guzzle class (ResponseInterface, BeforeEvent, ...).
    I've installed guzzle/guzzle bundle using the symfony3 requirements (
    But theses classes seems missing.

    Can you help?