We use cookies on this site to enhance your user experience. By clicking "OK, I Agree" or using our site, you consent to the use of cookies unless you have disabled them.
View our cookie policy to learn more.

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.

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.

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!

2019-05-10Vladimir Sadicov

Hey @alex

Mainly everything will be the same, but of course there are some changes in configuration, class and variable names Lixik JWT has good UPGRADE documents which show differences between versions, And you can follow them to build everything in right way!

Cheers!

2019-05-10alex

Hey! Is the lexik jwt implementation done in a different way between Symfony 3 and 4? I'm building my api with Symfony 4 and i'm not sure if some things should be done differently.

Sorry for the confusion and thanks for sharing your findings :)We will discuss about your suggestion because we really want to avoid this kind of problems to others

Cheers!

2018-07-09Amy

Yay! I figured it out. The tests moved since the last tutorial. The are in /tests now, not the ones in /src/AppBundle/Tests . I strongly suspect the people below had the same issue. Maybe you all should remove the copy from the old location?

2018-07-09Amy

I have downloaded the course code and done the setup on PHP 7.1 (It doesn't work on 7.2) . I'm running into a weird issue. When I try to run the tests with the filter, it says so tests executed. If I run tests without the filter, it says everything passed, but I'm not confident that it's actually running the tests in ProgrammerTestController because I've put in die statements or actual parsing errors, and the tests pass without a filter. I've made no modifications to the code other than to put in the very first test, which should fail.

I'm stumped on this one. Any thoughts?

2018-07-02Shaun

Thanks for such a detailed explanation weaverryan , this has made the subject loads clearer for me, I really appreciate it!

1) 100% traditional HTML web apps: the user's browser sends a request to /app & gets back HTML. There is NO AJAX, every page is a full refresh This is 0% API

2) 100% single-page application. In this model, there is literally 1 HTML file that the user gets when they access "/", and this is probably mostly an empty page with a div on it and some JavaScript. Then, 100% of all other requests are AJAX/API requests, which are used to build everything for your application.

3) A mixture :). And this is still the most common (though single-page apps, SPAs, are becoming more and more common). This site is a good example of a "mixed" app. When your browser makes a request to "/", you get a traditional HTML page, and there is no AJAX. But, if you go to, for example, a challenge page (https://knpuniversity.com/s..., this returns HTML... but the original HTML is actually mostly empty. We then make several AJAX requests in order to build the page. Whenever you change a file in the editor, more AJAX/API requests are made.

So yes, there is ALWAYS at least *one* HTML response. But, once that HTML responses is received, you could build a JavaScript app that makes many API requests. Or, (and I think this really gets to your question) if you really only need to print some data on your page, you can just use the data to print all of the HMTL you need and return it. In that case, there is no need to make any API request: your page is fully loaded and ready to go. Heck, even if you DO want to get some JSON data in order to use with JavaScript, you can (if you want) avoid an AJAX request by making that data available inside your HTML - e.g. https://knpuniversity.com/s..., or simply by setting your data to a global variable, like:

<script>// this will print as a JavaScript object, which you can then read from JavaScriptwindow.SOME_GLOBAL_DATA = {{ someDataArray|json|raw }};</script><script src="{{ asset('path/to/some.js') }}"></script>

I hope that helps!

2018-07-01Shaun

Architecture question!

In a traditional Symfony web app a user would click on a link, a request is sent to the server, and then the server sends back the html for the browser to display.

With a REST application it seems there needs to be (at least) 2 end points for any page that has data from a database. Here is my flow.

1. User sends request i.e. /app2. Server sends back HTLML for page3. Data is required so a javascript file calls an end point to retrieve this i.e. /app/api/posts4. Javasctipt receives JSON and adds the data to the page.

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-30Uns Rhandour

Hello,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()).

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 ?

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-02Victor 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.

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-24Mihail

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-21weaverryan

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

Thanks!

2016-06-20Mihail

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-10weaverryan

Haha, well done :)

2016-06-10Chuck 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-09weaverryan

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: https://gist.github.com/wea... - I hope it's helpful :).

Cheers!

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

2016-06-09Chuck 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 (https://github.com/nullziu/...But theses classes seems missing.