Reply to comment

CORS, Angular JS, and Parse.com, together, didn't let me login twice.

Things were going well with a re-architecting and re-factoring of a service to use Angular's awesome $request, and Django REST Frameworks' awesome ModelViewSet generics. As usual, when things are chugging along, you come across a weird bug that just sucks you in for a while. The bug I hit today involved CORS, AngularJS, and Parse (we're using Parse for part of our backend).

The symptom was that, if I logged in once, then logged out, I could not log in again. I could reach the server, but it wouldn't let me do the exact same thing I'd done just 30 seconds before.

It led to a deep dive reading the CORS standard, looking at a lot of Stack Overflow, and poking around my code, and dumping debug info. No luck.

Eventually, after several hours, I found the bug by looking at the debugger in Chromium. I'd been using Firefox and Firebug, but the error message wasn't helpful (it turned out). Firefox just reported the bug as a CORS error. Chrome's message told me why I was being disallowed from talking to the server. I was sending an X-SessionToken header, and CORS hadn't explicitly allowed that header via the Access-Control-Allow-Headers header.

So, when the browser saw the request contained a header that wasn't in the listed headers, it refused to make the connection to the server.

X-SessionToken is Parse's session management token. If you have it, you're logged on, and if you lack it, you're not.

Though the app in the browser doesn't interact with Parse's servers directly, because we host our own server for the webapp, the webapp uses X-SessionToken to make sure the session is valid (just like Parse does). Why? Because it makes it easier to test some code against Parse.

Why was it sending it? Because when the user logs on, I used Angular's $http.defaults.headers.common object to specify some default headers, so I wouldn't need to add them all over the code. When the user logged off, I didn't delete that value.

My fix is a little weird. I decided to allow the X-SessionToken header, and if I saw it in the headers, spit out an error message. This way, the programmer gets an ugly alert about what's happening.

	if ($headers['X-SessionToken']) {
	    header('X-AnnoyingError: The X-SessionToken is still set.  This is a client side error.  When you log out, the X-SessionToken must be removed from any default headers sent to the server.');
	    echo "The X-SessionToken is still set.  This is a client side error.  When you log out, the X-SessionToken must be removed from any default headers sent to the server.\n";
	    exit();
	}

Then I fixed the bug in my Angular JS code.

I did this because there may be multiple clients written to use this data. The error might happen again.

Some Gotchas

One thing good about these bugs is you get to learn some antipatterns and problems.

The first problem is needing to hit the OPTIONS over and over. The fix is to enable caching via the "Access-Control-Max-Age: 3600" header. Set it to the number of seconds you want to cache your response. The response will be retrieved from the cache if ALL the headers are the same.

(This CORS cache is supposed to be separate from the regular response cache, but it may not be on some Android browsers.)

The big antipattern is the header "Access-Control-Allow-Origin: *". That allows any page to access the API. That's fine for public APIs, but not for private ones. At the very least, test against a list of URLs. "example.lo" is my local development server.

	$allowed_origins = array(
	    'http://example.com',
	    'https://example.com',
	    'http://example.lo',
	    'https://example.lo'
	);
	$headers = apache_request_headers();
	$origin = $headers['Origin'];
	if (!$origin) {
	    header('HTTP/ 403 No Origin Header');
	    echo "No Origin header.\n";
	    exit();
	}
	if (!in_array($origin, $allowed_origins)) {
	    header('HTTP/ 403 Bad Origin Header');
	    echo "Bad Origin.\n";
	    exit();
	}

It's not that hard to improve the CORS interaction.

Within Django and DRF, you can do what the docs recommend, and use djang-cors-headers.

The CORS proponents are just suggesting "*" because it's easy.

The other antipattern I saw was in my own code (and it still isn't fixed). When you have big states like "logged in", you need methods to enter and exit those states cleanly. I had that, but the code was ugly, and didn't follow the common pattern of:

   function enterState(x) {
       taskA(x.userid);
       taskB(x.something);
       taskC(x.somethingElse);
   }
   function exitState(x) {
       undoTaskC(x.somethingElse);
       undoTaskB(x.something);
       undoTaskA(x.userid);
   }

Furthermore, taskA and undoTaskA should be used to encapsulate whatever it does, so you can do things like store away the existing state, and restore it later. It's a lot of functions, but you don't end up with the word salad of setting and unsetting variables, calling different functions in different libraries that all operate just a little differently, and other typical mess.

Like it or not, you're eventually going to edit the contents of enterState/exitState, probably to bolt on some external service or some other nonsense.

The last tip: run the application in both Chrome and Firefox, and look at the error messages in both. I have to remember that one.

Reply

The content of this field is kept private and will not be shown publicly.
  • Lines and paragraphs break automatically.

More information about formatting options

11 + 8 =
Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.