ember-ajax
ember-ajax copied to clipboard
Consider adding CSRF protection with Transparent Token security pattern
It seems appropriate that Ember Ajax should nudge developers towards good security practices by making it easy to implement good security practices. Transparent Token is protection against Cross-Site Request Forgery (CSRF).
Transparent Tokens mechanism works in the following way:
- Server provides CSRF token in the cookie
- Ember Ajax will check for the presence of this key in the cookie
- Ember Ajax will add the key's value into the headers of the request
- The server will verify the header against provided cookie value
Seems like a relatively straightforward implementation. Any reasons why we should not do this?
Here is a screenshot from presentation from presentation by Philippe de Ryck.
You can watch description of Transparent Token pattern here
Definitely a good idea to do something like that. AngularJS does a similar thing, by automatically copying the XSRF-TOKEN from a cookie to a header, if present. More info here: https://docs.angularjs.org/api/ng/service/$http
Is the name of that cookie something that's standard? Or would we need to provide a way for people to change that?
As far as I know, there's not really a standard name for the cookie. Server-side frameworks/addons/libraries generally offer a way to configure the name, so I don't think there's much of a downside to pick one fixed name for the protection cookie.
One example library is csurf for Express (https://github.com/expressjs/csurf), which allows you to configure the name of the cookie. This is the only thing you'd need to configure, as they already look for the X-XSRF-TOKEN header in incoming requests by default.
To make the comparison to Angular again, I don't think you can configure the automatic behavior. You can always roll your own code if you need something special, something along the lines as shown in the picture above.
I personnaly use django as backend, the cookie token is named csrftoken and the header token is named X-CSRFToken: https://docs.djangoproject.com/en/dev/ref/csrf/#ajax
The mechanics is similar but all names are differents.
Maybe the best approach here would be to add some test cases or example code that show how to set this up manually, if there isn't a consistent way that we could implement it from our end.
It seems that it only makes sense to settle on a default options (or a set of defaults) if there are frameworks that support this out of the box. That way, the dev automatically gets CSRF protection.
Regardless whether we settle on a default configuration, it seems that the most important aspect of this is actually informing developers that they should enable CSRF protection on both backend and frontend.
How would we do the latter? Add it to the official docs?
We had at one point talked about making a website for ember-ajax but I don't think that's really necessary. Maybe we can add it to a Wiki page? I would really like to avoid adding even more to the bloat of the README and start making (and linking to) Wiki pages.
Sounds fine to me. Let me know if I can help by providing some text to fit into the outline.
Since ember-ajax is only used in specific scenarios, I presume it would make sense to extend this solution towards other means of API access (e.g. ember-data)? Should this issue be documented on a higher level then?
Possibly. There is also talk of removing the networking code from Ember Data and having it actually leverage Ember AJAX under the hood but I have no idea what the timeline on that will be.
If someone wants to write up some documentation on this, that would be great.
Like I said, I don't mind writing something up on the topic. I think it's best if you create a page with an outline of how you'd like to inform people. I can fill in the blanks with a bit of info about the problem, and the solution.
We'll write it up for ember-ajax first, and then see how far up the chain we can take it :)
I think @taras should take care of that, seeing as he started this issue.
Would there be an easy way to disable this if it was added? The majority of use cases that I have, we're passing a token that the server requires so a CSRF is unneeded. Also, we utilize a lot of CORS, in which case a CSRF can't be used.
@webark I think we've landed on the fact that this wouldn't be a code change, but rather documentation about how to do this if you want to. Unless we can implement a pattern that works for everyone, I would be hesitant to ship with support for something specific, and from the conversation earlier is seems like it'll depend on the backend. Unless I'm totally mistaken (and please, someone tell me if I am 😉 )
ahhh.. I get it now. That makes sense. Thanks!
Is there any work to be done here now? Or can we close this issue?
For many API-only services CSRF isn't an issue (since there is no token tied to the domain). For example, of the session token is stored in local storage and passed along in a header.
For another category it's sufficient to add a custom header to all XHRs (e.g. X-My-Custom-Header: 1 and verify that it's set on the server (and reject requests otherwise). This is because the CSRF-vector use forms or flash to submit requests, and they cannot set custom headers.
There are some cases where CSRF must be considered (for example in mixed environments, where both JS and forms may create legitimate requests), but they are getting fewer as many are moving towards single-page apps and API-only backends.
Just wanted to note this in case others are stumbling upon this issue and are curious about CSRF.
I'd vote for just closing this issue. Possibly adding a note along the lines above to the readme under a CSRF heading.
Thanks for picking this back up. You actually touch upon a few different things here, and I feel that I can clarify things even a bit further.
- You're right about the fact that CSRF does not matter if you're adding session information in a custom header. However, the fact that you use an API-only backend does not mean cookies are not a valid mechanism to keep session state.
The essence here is that if you use cookies, then you need to make sure you have CSRF covered
- Adding a custom header to all XHRs to prevent CSRF may work to filter out non-XHR requests, but this quickly brings you in the realm of CORS. This means that you'll need to make sure that incoming requests are from an origin you trust (you can use the Origin header here).
The essence here is that if you only offer an API (and use cookies), you should check the Origin header against a list of trusted origins. In addition, you can add the custom header as described above to ensure that only XHR can make such requests.
@philippederyck I agree! Thanks for explaining this in a clear manner (you made a better job explaining it than I did). 😄
Perhaps we could just add a short section to the README.md under a CSRF heading and then close this issue. Would you mind PR:ing a text?
You're welcome. I will put this on my TODO list for the coming weeks. I'm currently swamped with a couple of training courses, but I'll write something up in the first half of May.
Remind me if you think I may have forgotten about this!
@philippederyck As the issue's still open and there's nothing obvious about it in the README, here's the reminder. :)
Hello @philippederyck where can I find the CSRF section for Ember Ajax or if someone already has some documentation or example I would really appreciate it.
I would also like to know how CSRF can be implemented for Ember Data.
Wow, talk about something from the past. I unfortunately never got around to addressing this, and I haven't worked with Ember for a couple of years ...
These days, for SPAs backed by APIs, I typically recommend a tight CORS policy as a defense. This works well if ...
- Do not perform state-changing operations with GETs
- Require a JSON content type on all POSTs
If you really want to make sure there's no way to bypass CORS with CSRF, you could also require the client to include a custom header (e.g., X-Force-Preflight: 1)
For others reading this:
If you really want to make sure there's no way to bypass CORS with CSRF, you could also require the client to include a custom header (e.g., X-Force-Preflight: 1)
This is an easy trick to shut down CSRF in an SPA! Note that this header name can be anything, as long as it's custom and that the backend require it.