CSRF and CORS with Django (REST Framework) - javascript

We're in the process of moving our frontend into a separate project (out of Django). It's a Javascript single page application.
One of the reasons is to make it easier for our frontend developers to do their work, not having to run the entire project -- including the API -- locally. Instead, we'd like them to be able to communicate with a test API we've set up.
We've managed to solve most of the CORS/CSRF issues along the way. But now we've run into something I can't find a solution for anywhere, despite reading lots of documentation and SO answers.
The frontend and the API are served from different domains (during development localhost and test-api.example.com). Until now, while served from the same domain, the frontend has been able to get the CSRF token from the csrftoken cookie set by the API (Django). But when served from different domains, the frontend (localhost) can't access the cookies of the API (api-test.example.com).
I'm trying to figure out a way to work around this, to somehow deliver the CSRF token to the frontend. The Django docs recommend to set a custom X-CSRFToken header for AJAX requests. Would we compromise the CSRF protection if we similarly served the CSRF token in every response as header and (via Access-Control-Expose-Headers) allowed this header to be read by the frontend?
Given that we've set up CORS properly for the API (i.e. only allowing certain domains to do cross origin requests to the API), JS on 3rd party sites should not be able to read this response header, thus not be able to make compromising AJAX requests behind the back of our users, right? Or did I miss something important here?
Or is there another, better way to achieve what we want?

I didn't understand your question at first, so allow me to summarize: you can't get the CSRF token from the cookie on the client because the Same Origin Policy blocks you from accessing cross-domain cookies (even with CORS). So you're suggesting that the server transmit the cookie to the client in a custom header instead, and are wondering if that's secure.
Now, the documentation does make a suggestion for how to transmit the token if you're not using the cookie: put it in the response body. For example, you could use a custom meta tag. When it comes to security I lean towards using recommended solutions rather than trusting my own analysis of something new.
That caveat aside, I don't see any security problem with what you're suggesting. The Same Origin Policy will prevent a third-party site from reading the headers just as it will the body, and you can opt in to reading them from your client domain with the CORS Access-Control-Expose-Headers header.
You might find this answer interesting, as it lays out the advantages and disadvantages of various CSRF token schemes. It includes the use of a custom response header, and—to the point of your question—confirms: "If a malicious user tries to read the user's CSRF token in any of the above methods then this will be prevented by the Same Origin Policy".
(You might want to look into whether you need Django's CSRF protection at all with your SPA. See this analysis, for example. That's outside the scope of this question, though.)

Assume you already have corsheaders installed. Write a Django middleware and include it in your MIDDLEWARE settings:
from django.utils.deprecation import MiddlewareMixin
class CsrfHeaderMiddleware(MiddlewareMixin):
def process_response(self, request, response):
if "CSRF_COOKIE" in request.META:
# csrfviewmiddleware sets response cookie as request.META['CSRF_COOKIE']
response["X-CSRFTOKEN"] = request.META['CSRF_COOKIE']
return response
expose the header in your settings:
CORS_EXPOSE_HEADERS = ["X-CSRFTOKEN"]
When you make a GET API call from you JS, you should get X-CSRFTOKEN from response header, go ahead and include it in the request header when you make POST PUT PATCH DELETE requests.

Related

Sending information cross-domain without touching server-side

This is a research question I've been hitting my head on for a while now. I'm not sure if it's possible but it seems like it should be.
How do I make var token that was defined in them.com's javascript to be visible to us.com's javascript WITHOUT sending information to any of the two servers?
The goal is to save information in the browser to be accessible cross-domain? localStorage, sessionStorage are domain-locked. And set-cookie make it visible to the server... So I cannot use those.
You could give CORS Anywhere a shot.
https://cors-anywhere.herokuapp.com/
CORS Anywhere is a NodeJS proxy which adds CORS headers to the proxied request.
Basically, you can prefix the request URL with https://cors-anywhere.herokuapp.com/.
For example:
fetch('https://cors-anywhere.herokuapp.com/http://your-req-url.com/some-endpoint')
You can also host it yourself. Further reading: https://github.com/Rob--W/cors-anywhere/

Setting request header, in a URL?

We have a webservice that is mainly intended to be called from javascript, via jquery's $.ajax(). When we call methods from javascript, we set a security token in a request header. If it's not there, or if it doesn't validate, we return an unauthorized error.
And that's all working fine.
But now we're faced with returning image files. So instead of having javascript call $.ajax(), we're embedding an image tag in the DOM:
<img src='http://mywebservice/imagescontroller/getAnImage?imageid=123'/>
And when we do that, we don't have our security token in the request header. I can think of two "easy" fixes. 1., we simply allow anonymous access to our image URLs, or 2., we pass the security token as a URL parameter.
The first choice is, of course, not a good idea. The second is straightforward enough. But before I settle on this approach, I was wondering if there was some easy way of setting request headers on these sorts of requests, that I was missing.
Ideas?
Easy fix: Use session cookies. That is a cookie without a expiry date. It will automatically transmit with each request and go away as soon as the users closes the browser, or you delete the cookie via javascript.
You simply store your token there and get it delivered for free to your server code.
Have some demo stuff here:
How do I set/unset cookie with jQuery?
If you run the services on another domain, you will need to use CORS to make the AJAX running - otherwise your AJAX will run into the Same Origin Policy. With CORS you can even make the cookies work.
See here: CORS request - why are the cookies not sent?
If you do not want to use CORS, you could also incorporate the service domain into your own via reverse proxying. This will solve the SOP problem as well as make the use of cookies possible. Setting up a reverse proxy within Apache is pretty straight forward.

Is a SSL'ed JSON API that uses cookies for authentication and nonces generally secure?

If I build an SSL'ed API that authenticates with a session ID held within a cookie, adds a nonce as a query parameter, and always responds with a JSON 'Object' response (as opposed to a JSONP-style response with a callback), is it secure in general, and in particular against XSRF?
The intent with such an API to only have it available to pages on my own domain, and to be free to expose private data (such as username and emails) through this API (but not be consumable by other domains)--and retain a reasonable amount of simplicity for developers on the team.
Let me at least share what I understand about this approach, and why I think it's secure. Please enlight me if wrong!:
A <script> tag dropped on a 3rd-party domain to our site would send my cookies, but would not be able to parse the JSON object response (and the response would always deliberately be a JSON object at the top level). Also, I need to make sure that API calls that affect state on the server are all protected by non-GET method access, because <script> tags must use GET and so can not cause havok by attempt to call state-changing calls (in other words, the API would be adherent to REST in so far as HTTP methods go). Also, I deliberately do not support JSONP because it would be a security hole.
Man-in-the-middle used to hijack cookies (the session) is not a concern because I'm using SSL with valid certificates.
Replay attacks are a temporally limited concern because of the use of a nonce will limit how long one could send in a replay of an HTTPS request, because the server will make sure that the API call is only valid for a small amount of time in a typical nonce-validating way.
XMLHttpRequest can not make cross-domain calls, so it can't request anything from my site.
CORS (Cross ORigin Resource Sharing) is not of concern because I don't have a crossdomain.xml file or any other advertisement of cross-domain support associated with HTML 5.
An iframe in a 3rd-party site doesn't matter because even though it can load my page graphically, the host site can't access any data within that iframe, and because I've made no attempt to support cross-domain iframe communication (so they can attempt to set # on the iframe URL like folks do to enable communication between cross-domain iframes, but my page won't be responsive to it).
EDIT:
A nonce would also protect against even cross-domain GET requests (i.e., <script> tags) as russau says. In thinking on that specifically, I like the idea of asking for a nonce in a 'POST' API call that is not itself nonce protected; it should be the case that only XmlHTTPRequest's on the same domain can then generate a nonce to begin with. This seems to be a simple way of making the generation of nonce's developer-friendly. (i.e., nothing server-side for the website/javascript developers--just ask for your nonce from the same API you are using to develop against, and make requests with that nonce until you get a 'bad nonce' response--then ask for a new one, and repeat.
The only attack I can imagine is DNS rebinding. If your webserver is configured properly (a name-based vhost should be sufficient) you should be pretty safe though.

Javascript API hindered by Cross Domain API calls

I need to provide a functionality similar to "Share with Facebook" for my social networking site. Facebook uses nested iframes and also xd_receiver concepts. I want to write a JavaScript API(JS file hosted on my domain), which can be used by different sites to call my web server APIs in order to share, post or recommend on my social networking site. I have a few questions -
Even though I provide the JS API, and diff sites load the JS file using the source, if any API call is made, it will again be a cross domain call(If I am comprehending correctly) and will be rejected on the server?
How to overcome such situation?
Is there any other better mechanism to implement this functionality?
Please suggest so that I can proceed with the implementation.
I think the default way is to use jsonp to get around cross domain limitation. http://en.wikipedia.org/wiki/JSONP. It might require a change in your api though. A user requests your api through the src of a script tag passing in a function callback. Your api would return pass your json response to the function specified.
Do you know why they use iframes and not simple get requests with JSONP/Images/scripts?
The answer is security. I cannot write a script that clicks their button which will automatically "like" the page.
Using plain old JavaScript with a JSONP will allow the developer to automatically click the button. Do you want that to happen?
The requests are made by the browser and not from the JS file, so, your requests will be cross-domain every time they did from another domain site.
Your server will only reject cross-domain requests if you implement a referrer validation.
And you can use JSONP if your API needs custom contents from your site...
To allow cross domain requests, you need to set the following Header in your HTTP Response:
Access-Control-Allow-Origin: *
The implementation will vary depending on the back-end you are using.
If the host in the Origin header of the request is anything but the host of the request, the response must include the listed Origin in the Access-Control-Allow-Origin header. Setting this header to * will allow all origins.
For very specific information on cross origin resource sharing see http://www.w3.org/TR/cors/. If you're not big on reading w3c documents, check out MDN's primer.
Note: Internet Explorer does its own thing with regards to cross domain requests. This answer is a good start if you have issues with IE.

Is it safe to serve jsonp if I require authentication headers?

I want to serve jsonp so other sites can get json data from my site. I understand that this would be dangerous if I used cookies to authenticate users, because browsers would send the cookies with all requests to my site, so a malicious page could make authenticated requests on my users' behalves without asking them.
All requests to my service have to be authenticated with a special header set on the request, X-AG-AUTH. A secret token identifying the user must be set in that header.
Would a malicious site be able to get data from my service via jsonp without the user providing the secret token?
Well, requiring a custom header for a jsonp call would render the jsonp call useless for requests coming from other domains, because your callers wouldn't be able to set those headers.
You could use a somewhat similar approach: require a CSRF-prevention-style token passed as a parameter in a POST request. This would require you to share both the logic for generating these tokens and a secret key with each site you want to allow to call your endpoint. Of course, if any of those keys were ever compromised on the remote server's side, you probably wouldn't know about it until it was too late.
If you're willing to forgo functionality for folks with really old browsers, you could use regular JSON over CORS* with a parser-breaking prefix to prevent cross-site script inclusion.
I'm assuming your data is not something you want to be made public, in which case you're hopefully also requiring SSL.

Categories