16

I have a basic AngularJS service setup like so:

app.factory('User', function($resource) {
return $resource('http://api.mysite.com/user/:action:id/:attr', {}, {
    history: {
        method: 'GET',
        params: {
            attr: 'history'
        }
    },
    update: {
        method: 'POST',
        params: {
            name: 'test'
        }
    }
});
});

and I use it like this:

User.history({id: 'testID'}, function(data) {
    console.log('got history');
    console.log(data);
});
User.update({id: 'me'}, function(data) {
    console.log('updated');
    console.log(data);
});

Problem one: User.update(), despite having the method set to POST, keeps sending OPTIONS as the request method.

Though Chrome Dev tools reports the request header Access-Control-Request-Method:POST is sent as well (Not sure if that means anything).

Problem two: I keep getting an error with CORS, despite having these headers set in the API code:

header('Access-Control-Allow-Origin: *');
header("Access-Control-Allow-Methods: PUT, GET, POST, DELETE, OPTIONS");

This problem only shows up though if making a non-GET request.

What's the proper way to be handling this? I've also looked into JSONP, but with this being a RESTful api, I'm not sure how to get around the problems with only GET support.

Narretz
  • 4,769
  • 32
  • 40
Lucas Raines
  • 1,315
  • 3
  • 15
  • 24

3 Answers3

14

Your two problems are actually one problem. The OPTIONS request is part of the CORS process. For POST requests, the browser first sends an OPTIONS call, and the server responds if it is okay to execute it.

If the OPTIONS request fails, Angular / Chrome shows you the reason in the console. For example:

OPTIONS https://*** Request header field Content-Type is not allowed by Access-Control-Allow-Headers. angular.min.js:106

XMLHttpRequest cannot load https://***. Request header field Content-Type is not allowed by Access-Control-Allow-Headers. 

You probably have to set Access-Control-Allow Headers on the server, too:

header('Access-Control-Allow-Headers: Content-Type, x-xsrf-token')

x-xrsf-token is for angular' to prevent CSRF. You may have to add more headers, depending on what you send from the client.

Here is a very good guide on CORS: https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS

Narretz
  • 4,769
  • 32
  • 40
  • The browser sending the OPTIONS request then the POST request was where my problem was. The server wasn't set up to respond to the initial OPTIONS. I didn't need to do anything with x-xsrf-token though. Used the code from here to handle all the necessary headers: http://stackoverflow.com/questions/13293157/backbone-slim-php-access-control-allow-headers-can-get-information-cant/13530329#13530329 – Lucas Raines Jun 26 '13 at 02:54
  • 1
    this was exactly my problem. thank you. If you use node/express, follow the "enable pre-flight" instructions here to get cors working: https://github.com/troygoode/node-cors/ – JasonS Sep 18 '13 at 03:21
7

In AngularJS to make CORS working you also have to overwrite default settings of the angular httpProvider:

var myApp = angular.module('myApp', [
    'myAppApiService']);

myApp.config(['$httpProvider', function($httpProvider) {
        $httpProvider.defaults.useXDomain = true;
        delete $httpProvider.defaults.headers.common['X-Requested-With'];
    }
]);

Just setting useXDomain to true is not enough. AJAX request are also send with the X-Requested-With header, which indicate them as being AJAX. Removing the header is necessary, so the server is not rejecting the incoming request.

Note: Answer only works for older AngularJS version previous to 1.2. With 1.2 and above you don't have to do do anything to enable CORS.

Torsten Engelbrecht
  • 13,318
  • 4
  • 46
  • 48
  • 6
    This actually wasn't necessary, as I have access to the api server and added `header('Access-Control-Allow-Headers: X-Requested-With');` which I assume fixes the problem you're talking about. – Lucas Raines Jun 26 '13 at 20:04
  • Good tip. I wasn't aware of setting the allow headers header. Thx. – Torsten Engelbrecht Jun 27 '13 at 09:52
  • maybe you had to do this with old angular versions, but I know that in angular 1.2.x, you do not need to do this. Following @Narretz's answer works for me. – JasonS Sep 18 '13 at 03:24
  • 3
    @TorstenEngelbrecht useXDomain is an option from a rejected pull request that _never existed_, doing useXDomain = true will accomplish __nothing__. – Benjamin Gruenbaum May 15 '14 at 14:06
  • @BenjaminGruenbaum was not aware of that. I am wondering why it did not work without it to this time. Anyway, this answer is already pretty old in Angular terms, so its most likely outdated. I am not using CORS since then and never touched that area again so far. – Torsten Engelbrecht May 15 '14 at 15:10
0

Better to solve this problem at the server. On apache you can solve it like this in a .htaccess file. This is a source of pain for angular development and can be solved in angular as well but its probably not the best way to do it.

Header set Access-Control-Allow-Origin "*"
Header add Access-Control-Allow-Headers "origin, x-requested-with, content-type"
Header add Access-Control-Allow-Methods "PUT, GET, POST, DELETE, OPTIONS"
Caleb Kleveter
  • 11,170
  • 8
  • 62
  • 92
rotato poti
  • 121
  • 1
  • 7