The only tricky thing is that we only want to require the Content-Type header when the user is requesting an API endpoint. In our application, this means all the endpoints inside of RepLogController. So, we could see if the URL starts with /reps... but that could get ugly later if the API grows to a lot of other URLs.

If your app is entirely an API, that's easy! Or if all the URLs start with /api, that's also easy to check.

But, in our app, let's use a different trick... which is gonna be kinda fun.

Above the controller class, add @Route() with defaults={} and a new flag that I'm inventing: _is_api set to true.

When you put an @Route annotation above the controller class, it means its config will be applied to all of the routes below it. Now, inside of the subscriber, we can read this config. To see how, add dump($request->attributes->all()) then die.

Try it! Nothing changes. But now, in ApiRoute, go to the Code -> Generate menu - or Command+N on a Mac - and override the getDefaults() method. Return a merge of _is_api set to true and parent::getDefaults().

Back in the subscriber, remove the dump. Then, if !$request->attributes->get('_is_api') return. And now that we know were only operating on API requests, check the header: if $request->headers->get('Content-Type') does not equal application/json, we have a problem! Create a new 400 response: $response = new JsonResponse().

The data we send back doesn't matter - I'll add a message that says what went wrong. But, give this a 415 status code: this means "Unsupported Media Type". Finish this with $event->setResponse($response). This will completely stop the request: this response will be returned without even calling your controller.

Ok, let's try this! Find the rep_log_api.js file and look down at createRepLog. We are setting this Content-Type header. So, this should work! Move over, go back to /lift and refresh. I'll open my network tools. And.. yea! It totally works! But try to delete a rep log... failure! With a 415 status code.

This is because the DELETE endpoint does not set this header. And... hmm, it's kinda weird... because, for the DELETE endpoint, the body of the request is empty. There's some debate, but, because of this, some people would argue that this request should not need anyContent-Type header... because we're not really sending any JSON!

But, by requiring this header to always be set, we give our application a bit more security: it removes the possibility that's somebody could create a CSRF attack on that endpoint... or some future endpoint that we don't send any data to.

In other words, we are always going to set this header. Remove it from createRepLog and go up to fetchJson() so we can set this here. The only tricky thing is that it's possible that someone who calls this will pass a custom header, and we don't want to override that.

And we are protected from CSRF! That's because, first, we do not allow other domains to make AJAX calls to our site and, second, all of our API endpoints require a JSON body - which we explicitly required by looking for the Content-Type header.

Oh my gosh.... we're done! That's it, that's everything! If you've made it all the way through, you rock! You have the tools to create the craziest frontend you can think of! And yes, there are more things in React that we could cover, like the React router or Redux, which adds a more complex architecture on top of React, but helps solve the problem of passing around so many props.

But, these are extras - go get some real-world success with React and report back! We'd love to know what you're building.

Alright people, seeya next time.

Leave a comment!

Ah! So if you want external programs to use your API, that's different. That IS a more appropriate use-case for having API tokens. What I'd recommend is *two* authenticators: one for the normal, session-based authentication and another that allows token-based authentication. Then, you can use the session-based login for your JavaScript app and allow the external applications to send an API token.

If you have some more questions about this, I'd be happy to answer :).

Cheers!

2018-08-23Askan Simon

Dear weaverryan, thank you very much for your detailed answer.I thought lot about it and came to similar conclusions,and have rebuilt the app sessionbased like in your tutorial. But there is a disadvantage. How can I share the Api with other programs, that do not use the frontend? I thinka new Route ("apiextern") and in additional authorisationetc. is the solution ;) Best Regards Askan

Great question :). Your first option is ... don't use API keys! Just use session-based storage and cookies like we do in this tutorial. It's simple & secure (as long as you have your user use HTTPS, which you always should). But yes, I see you are already using API keys ;).

The issue with API tokens is where to store them? And, honestly, there's a lot of conflicting info about this. For example: https://dev.to/rdegges/plea... - according to this resource, using local storage is not an option (though it would be really simple!). Other places - https://auth0.com/docs/secu... (which is a great site I trust) - absolutely show local storage being used - they even show it in their quick starter docs: https://auth0.com/docs/quic...

So... there is clearly some security issues with using local storage... though *how* big of a deal, is debated :/. You're right that putting the API key as a global JavaScript variable is not a good idea. Well, it's actually the same as "local storage" - other JavaScript running on your page could access it.

So, use local storage... or if you really care about security, use sessions & cookies :). That's probably not the exact answer you wanted, but that's the state of things :).

Cheers!

2018-08-18Askan Simon

Hello. Thank you for the great tutorial. I already had an API with ApiKeys:https://symfony.com/doc/cur...everything works great. But I do not want to build a Single Page Application,how can React remember the ApiKey between two page views? I think it's not so good to hand it over via Twig ;) (I am a beginner with React...) Thank you

You're just *barely* too early - the video is posted now :). And, I'm *thrilled* you found the course useful - I had such a great time writing it with Franks help! And yes, Redux is absolutely something we're thinking about... it's not a certainty yet... we're waiting to see more people ask for it. However, this tutorial has been very well-received, so I think there will be good demand to keep going with Redux.

Cheers!

2018-08-15Milan Vlach

Cool guys!

I am a little bit too soon here, but that is because you made such a great course which I have been returning to every day! What a ride! Are you planning on adding Redux course too? You could call it something like: "JavaScript for PHP Geeks: Redux for the legends (with symfony)". :imagine_sun_glasses_emoji_here: