09.
Lock down: Require Authentication Everywhere
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
Whoops, an error! Please, try again later.
12 Comments
Hi,
First of all sorry if I posted this question in wrong part of the tutorial.
I just wanted to know if I understand it all correctly. So my questions are:
1. How do I secure SPA (Single Page Application).
2. Do I see the flow correctly?
3. Security?
1. Say I have API backend ready for my app. For example I can go to /api/posts and add new post. Now I want to build new VueJS component which will send data over post to this end point. Once I have my end point how do I secure it? I know how to attach token in tests(I even have my own testing tools!) but how do I attach it on every request? Should I attach it on every request?
Should I create this new token for example in guard login form auth?
2. The way I see the whole thing for now:
- I log into the system with hard credentials (login, password)
- After that I create web token right away and attach it to every request
- if it expires and I'm still logged in I will need to refresh it
- I store token somewhere?
3. If I see this correctly as long as I don't provide very sensitive informations, make sure that it uses HTTPS and token is not stolen I can assume I'm safe?
Thank you in advance.
Hope I didn't miss anything obvious.
Best Regards,
Robert
Hey Robert!
Good questions :). There are two approaches to handling authentication with a JS frontend:
1) You could just use cookies. This is not as trendy as tokens, but it's dead simple. Basically, it's how you've been doing AJAX for years. If you create a login system (with username, password), then your users browser will have a traditional session cookie (I believe you'll need to have the page refresh to set the session cookie, or set it manually if everything is done via AJAX). But once your session cookie is set... you're good! Your session cookie acts a lot like a token, it's automatically send on every request and authenticates your AJAX calls. This is a dead-simple approach. It's drawbacks are that other entities (e.g. iPhone app) can't use this way of authenticating to use your API and your JavaScript doesn't know anything about the user (i.e. if you want to know the users name or email in JavaScript, you need to store that somewhere manually - it's kind of "separate" from authentication).
2) You could use tokens (I recommend JWT)! You have the flow for this basically correct. When you originally authenticate, the response back to your user will include the token (similar to how we do it in this tutorial - so yes, the token would be created in an authenticator and then returned on the successful JSON response). You'll then store this and attach it (e.g. as a header) on each subsequent request made by your API. Store the token in an HTTPS-only cookie to avoid XSS attacks. How to attach it on every request? It depends on what you're using for AJAX - most have a way to globally attach a header value to *all* requests.
Like with many things in life, the token approach is a bit more professional... but a bit more complex. For expiration, you can either set an expiration time... but then you need to require your user to log back in (or do something with refresh tokens, which can be complex). If you make your token last forever... then you'll need to add some storage on the backend with a list of tokens that you have created (e.g. you could give each JWT a unique id, then have a table of these) so that when the user logs out (or you think they should be logged out, for any reason), you can set that token as expired. You'll check this whenever any request is made to your backend.
And yep, *always* use HTTPS (and store the token in an HTTPs-only cookie). In any authentication system, there is ultimately a key/token... and if this is stolen... well... all bets are off :). But if you're transferring over HTTPs and storing the cookie in the HTTPS only cookie, you've done your duty to protect the user from this.
I hope this helps! GREAT questions! Btw, you might like this article: https://stormpath.com/blog/...
Cheers!
Hi Ryan,
First of all thank you for such detailed answer.
I think I will go with JWT for two reasons: I want to learn how to use them and later on I will expose my api outside application so I will have to use some kind of token authentication anyway.
Just one thing to clarify: So I should attach header on JS side not on Symfony Side?
Thank you again. I will go through more material and provided article of course.
You rock!
Cheers!
Hey Robert!
> Just one thing to clarify: So I should attach header on JS side not on Symfony Side?
Yes, on the JS side. When you originally make the request to login (with hard credentials), your Symfony API will return the token (probably in JSON, like a normal endpoint). Then, on each request your JS makes to your API after, your JS will attach the token on a header. You will then *read* that header in Symfony (i.e. in your authenticator).
Good luck and cheers!
Hi Ryan,
Thanks for help!
Cheers!
Hi Ryan,
Is there a way to capture a request and do something with it (e.g. extract some values from it) before it gets to an action?
Is there a method that executes prior to each action? Perhaps a constructor in a Controller, or some pre-action method?
Thank you!
Hi Vlad!
GREAT question. There is no preAction method in Symfony - this stuff is typically done with an event listener.
However, what you need depends on what you're trying to accomplish. A lot of times, people want a "preAction" type of setup because they want to do some work and initialize some properties on the controller that are used by many/all different actions. For example, suppose you need to read a query parameter in all of your actions and then use it to calculate something. Instead of putting this logic in a "preAction" and setting the final result on a property, I usually create a private function that does this work and returns the value. Then, I just call this method whenever I need that work done. This also prevents that "work" from being done unnecessarily, in case there are some actions that don't need that value.
But, you may also have a totally different situation - let me know! In the REST world, sometimes there *are* things that you just want automatically done before the controller, and so creating a listener might be the right way to go. FOSRestBundle, for example, comes with several listeners that run before the controller and prepare some things from the request.
Cheers!
Thank you, Ryan!
I have used an event subscriber/listener, like you've suggested, with Kernel::REQUEST listener, and that certainly does the trick!
That's exactly what I'm trying to do: to initialize some properties on a controller that would be used by its actions.
I'm now able to extract the info I need from a request.
My next question is, where would I store these extracted properties, so they can be accessed by controller(s)? Is there a place I could store them, or do I need to create some sort of a service for that?
Thank you again for your great help.
Great work Vlad!
You have a few options on where to store it. You can definitely create a service and store them there. Or, there is one place on the Request object that is meant for storing "extra" stuff - the Request attributes:
Then, obviously, you can fetch those from the request in a controller. As an added benefit, if you put a key into the attributes, you can actually also have it as an argument to your controller. So, in this example, any controller could now have a
$someKeyargument. We talk about that here: http://knpuniversity.com/screencast/symfony-journey/magically-add-controller-argsLet me know how it goes!
Hi Ryan,
Thank you for your reply.
A few more questions.
How do I add additional parameters to the controller's constructor?
I'd also like to know whether the Request object is available in the constructor of the controller. I have put together a quick test and it looks to me that an event subscriber gets called prior to the constructor of the controller, and does have the Request object, yet the controller's constructor ain't got it. I am getting the Request object via the RequestStack in the constructor:
$requestStack = new RequestStack();
$request = $requestStack->getCurrentRequest();
and it appears to be NULL.
Could you please explain the order of execution, and at which point the Request object becomes available. I understand it is not available all the time.
Thank you!
Hey Vlad!
Yes, another set of great questions :).
1) Your event listener is called before your controller is instantiated (i.e. the __construct() is called) and execute (i.e. the action is called)
2) You have no control over the constructor arguments of your controller. Unless you choose to register your controllers as a service. I don't do this, but it's a perfectly valid option: http://symfony.com/doc/current/cookbook/controller/service.html. If you do not register your controller as a service, you also do not have access to the container - or any of the shortcuts like $this->get() - from inside __construct(). It's just too early - Symfony hasn't passed you the container yet.
3) If you did register your controller as a service and you wanted the Request object as an argument, you will inject the
`request_stack
`into your service. This will give you the RequestStack object - but you shouldn't instantiate it. Symfony already has this object ready for you - and when you call getCurrentRequest(), it will give you the Request :).
4) You probably already know this, but just in case: the Request object can also be an argument to your controller. And, continuing with my previous example, you can also get the request attributes as arguments (I mentioned this earlier, but didn't show the full code):
Hope this helps! You're doing some pretty cool/advanced stuff.
Cheers!
Thank you, Ryan!
"Houston: no signs of life"
Start the conversation!