266

I'm trying to setup AngularJS to communicate with a cross-origin resource where the asset host which delivers my template files is on a different domain and therefore the XHR request that angular performs must be cross-domain. I've added the appropriate CORS header to my server for the HTTP request to make this work, but it doesn't seem to work. The problem is that when I inspect the HTTP requests in my browser (chrome) the request sent to the asset file is an OPTIONS request (it should be a GET request).

I'm not sure whether this is a bug in AngularJS or if I need to configure something. From what I understand the XHR wrapper can't make an OPTIONS HTTP request so it looks like the browser is trying to figure out if is "allowed" to download the asset first before it performs the GET request. If this is the case, then do I need to set the CORS header (Access-Control-Allow-Origin: http://asset.host...) with the asset host as well?

sideshowbarker
  • 62,215
  • 21
  • 143
  • 153
matsko
  • 20,727
  • 21
  • 94
  • 138

14 Answers14

227

OPTIONS request are by no means an AngularJS bug, this is how Cross-Origin Resource Sharing standard mandates browsers to behave. Please refer to this document: https://developer.mozilla.org/en-US/docs/HTTP_access_control, where in the "Overview" section it says:

The Cross-Origin Resource Sharing standard works by adding new HTTP headers that allow servers to describe the set of origins that are permitted to read that information using a web browser. Additionally, for HTTP request methods that can cause side-effects on user data (in particular; for HTTP methods other than GET, or for POST usage with certain MIME types). The specification mandates that browsers "preflight" the request, soliciting supported methods from the server with an HTTP OPTIONS request header, and then, upon "approval" from the server, sending the actual request with the actual HTTP request method. Servers can also notify clients whether "credentials" (including Cookies and HTTP Authentication data) should be sent with requests.

It is very hard to provide a generic solution that would work for all the WWW servers as setup will vary depending on the server itself and HTTP verbs that you intend to support. I would encourage you to get over this excellent article (http://www.html5rocks.com/en/tutorials/cors/) that has much more details on the exact headers that needs to be sent by a server.

L1ghtk3ira
  • 2,330
  • 3
  • 26
  • 58
pkozlowski.opensource
  • 116,401
  • 59
  • 321
  • 285
  • 1
    Yeah this is what I thought was happening. Turns out I need to setup the access headers on the asset host to get this to work. – matsko Aug 24 '12 at 15:31
  • 23
    @matsko Can you elaborate on what you did to resolve this? I'm facing the same issue whereby an AngularJS `$resource` _POST_ request is generating an _OPTIONS_ request to my backend ExpressJS server (on the same host; but a different port). – dbau Oct 31 '12 at 14:22
  • The way that I got it to work is by setting up a CORS header on both the origin server and any of the servers that the remote requests are aimed at. In this case you would need to set it up for the server that delivers your JavaScript code and then do the same for the server that delivers the $resource data. – matsko Oct 31 '12 at 14:24
  • 6
    For all the down-voters - it is next to impossible to provide _exact_ configuration setup for all the web servers out there - the answer would take 10 pages in this case. Instead I've linked to an article that provides more details. – pkozlowski.opensource Aug 10 '13 at 13:15
  • 5
    You're right that you can't prescribe an answer - you'll need to add headers to your OPTIONS response that covers all of the headers that browser requests, in my case, using chrome it was the headers 'accept' and 'x-requested-with'. In chrome, I figured out what I needed to add by looking at the network request and seeing what chrome was asking for. I'm using nodejs/expressjs as the backed so I created a service that returned a response to the OPTIONS request that covers all the headers required. -1 because I couldn't use this answer to figure out what to do, had to figure it out myself. – Ed Sykes Nov 09 '13 at 14:00
  • 1
    To elaborate on 'POST usage with certain MIME types', these types are: application/x-www-form-urlencoded, multipart/form-data, or text/plain. ANYTHING ELSE triggers a preflight. See http://stackoverflow.com/a/12320736/331791 – alalonde Feb 21 '14 at 17:41
  • 1
    I'm a little bit lost, because jQuery make it so easy without any further configuration. – azuax Feb 23 '15 at 19:07
  • 2
    I know it's 2+ years later, but... The OP refers to GET requests multiple times (emphasis added by me): «[...] the request sent to the asset file is an OPTIONS request (**it should be a GET request**).» and «the browser is trying to figure out if is "allowed" to download the asset first **before it performs the GET request**». How can it not be an AngularJS bug then? Shouldn't preflight requests be forbidden for GET? – polettix Sep 13 '15 at 09:54
  • 4
    @polettix even GET request can trigger pre-flight request _in a browser_ if custom headers are used. Check "not-so-simple requests" in the article I've linked: http://www.html5rocks.com/en/tutorials/cors/#toc-making-a-cors-request. Once again, it is _browser's_ mechanism, not AngularJS. – pkozlowski.opensource Sep 13 '15 at 17:54
  • 2
    @pkozlowski.opensource I was living under the (false) assumption that GET requests are always "simple", not the case definitely: http://www.w3.org/TR/cors/ - many thanks for making me double check, lesson learned! – polettix Sep 15 '15 at 07:43
70

For Angular 1.2.0rc1+ you need to add a resourceUrlWhitelist.

1.2: release version they added a escapeForRegexp function so you no longer have to escape the strings. You can just add the url directly

'http://sub*.assets.example.com/**' 

make sure to add ** for sub folders. Here is a working jsbin for 1.2: http://jsbin.com/olavok/145/edit


1.2.0rc: If you are still on a rc version, the Angular 1.2.0rc1 the solution looks like:

.config(['$sceDelegateProvider', function($sceDelegateProvider) {
     $sceDelegateProvider.resourceUrlWhitelist(['self', /^https?:\/\/(cdn\.)?yourdomain.com/]);
 }])

Here is a jsbin example where it works for 1.2.0rc1: http://jsbin.com/olavok/144/edit


Pre 1.2: For older versions (ref http://better-inter.net/enabling-cors-in-angular-js/) you need to add the following 2 lines to your config:

$httpProvider.defaults.useXDomain = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];

Here is a jsbin example where it works for pre 1.2 versions: http://jsbin.com/olavok/11/edit

Kristian
  • 19,340
  • 14
  • 84
  • 156
JStark
  • 2,698
  • 2
  • 26
  • 36
  • This worked for me to. Here is the appropriate magic for the server side with nodejs/express: https://gist.github.com/dirkk0/5967221 – dirkk0 Jul 10 '13 at 15:25
  • 14
    This only works for GET request, still cant find solution for POST request on cross domain. – Pnctovski Jul 25 '13 at 06:36
  • 2
    Combining this answer with user2304582 answer above should work for POST requests. You have to tell your server to accept POST requests from the external server that sent it – Charlie Martin Aug 28 '13 at 20:33
  • This doesn't look like it will work on its own to me...so a -1. You need something on the same URL that will respond to an OPTIONS request as part of a pre-flight check. Can you explain why this would work? – Ed Sykes Nov 09 '13 at 14:07
  • 1
    @EdSykes , I've updated the answer for 1.2 and added a working jsbin example. Hopefully that should solve it for you. Make sure you press the "Run with JS" button. – JStark Nov 13 '13 at 17:31
  • I am using 1.2.3, and the $sceDelegateProvider did the trick for GET requests. I still have the same issue for POST requests. I updated my virtualhost with 'Header set Access-Control-Allow-Methods "GET, POST, PUT, OPTIONS"' with no luck. Any idea? – Ant Dec 02 '13 at 13:25
  • For pre-1.2, deleting the X-Requested-With would prevent the server from detecting ajax requests. Not an awesome solution. – Daniel Schaffer Apr 28 '15 at 18:59
  • @delboud, Here is an example working with 1.3.14 http://jsbin.com/fobetubufo/1/edit?html – JStark Apr 28 '15 at 20:33
  • @DanielSchaffer, not sure how to advise other than encourage migration to a newer version. – JStark Apr 28 '15 at 20:34
62

NOTE: Not sure it works with the latest version of Angular.

ORIGINAL:

It's also possible to override the OPTIONS request (was only tested in Chrome):

app.config(['$httpProvider', function ($httpProvider) {
  //Reset headers to avoid OPTIONS request (aka preflight)
  $httpProvider.defaults.headers.common = {};
  $httpProvider.defaults.headers.post = {};
  $httpProvider.defaults.headers.put = {};
  $httpProvider.defaults.headers.patch = {};
}]);
user2899845
  • 1,151
  • 9
  • 13
34

Your service must answer an OPTIONS request with headers like these:

Access-Control-Allow-Origin: [the same origin from the request]
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: [the same ACCESS-CONTROL-REQUEST-HEADERS from request]

Here is a good doc: http://www.html5rocks.com/en/tutorials/cors/#toc-adding-cors-support-to-the-server

bPratik
  • 6,428
  • 3
  • 31
  • 65
user2304582
  • 369
  • 3
  • 2
  • 1
    To elaborate on the [the same ACCESS-CONTROL-REQUEST-HEADERS from request] part, as I mentioned on another answer, you need to look at the OPTIONS request that your browser is adding. Then, add those headers on the service that you are building (or web server). In expressjs that looked like: ejs.options('*', function(request, response){ response.header('Access-Control-Allow-Origin', '*'); response.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE'); response.header('Access-Control-Allow-Headers', 'Content-Type,Authorization,accept,x-requested-with'); response.send(); }); – Ed Sykes Nov 09 '13 at 14:03
  • 1
    Ed Sykes comment is very accurate, be aware that the headers sent on the OPTIONS response and the ones sent on the POST response should be exactly the same, for example: Access-Control-Allow-Origin:* is not the same as Access-Control-Allow-Origin : * because of the spaces. – Lucia Aug 11 '14 at 23:38
20

The same document says

Unlike simple requests (discussed above), "preflighted" requests first send an HTTP OPTIONS request header to the resource on the other domain, in order to determine whether the actual request is safe to send. Cross-site requests are preflighted like this since they may have implications to user data. In particular, a request is preflighted if:

It uses methods other than GET or POST. Also, if POST is used to send request data with a Content-Type other than application/x-www-form-urlencoded, multipart/form-data, or text/plain, e.g. if the POST request sends an XML payload to the server using application/xml or text/xml, then the request is preflighted.

It sets custom headers in the request (e.g. the request uses a header such as X-PINGOTHER)

When the original request is Get with no custom headers, the browser should not make Options request which it does now. The problem is it generates a header X-Requested-With which forces the Options request. See https://github.com/angular/angular.js/pull/1454 on how to remove this header

Community
  • 1
  • 1
Grum Ketema
  • 201
  • 2
  • 2
10

This fixed my problem:

$http.defaults.headers.post["Content-Type"] = "text/plain";
demonplus
  • 5,036
  • 11
  • 41
  • 56
Cem Arguvanlı
  • 595
  • 9
  • 15
10

If you are using a nodeJS server, you can use this library, it worked fine for me https://github.com/expressjs/cors

var express = require('express')
  , cors = require('cors')
  , app = express();

app.use(cors());

and after you can do an npm update.

Heretic Monkey
  • 10,498
  • 6
  • 45
  • 102
Zakaria.dem
  • 292
  • 3
  • 9
4

Here is the way I fixed this issue on ASP.NET

  • First, you should add the nuget package Microsoft.AspNet.WebApi.Cors

  • Then modify the file App_Start\WebApiConfig.cs

    public static class WebApiConfig    
    {
       public static void Register(HttpConfiguration config)
       {
          config.EnableCors();
    
          ...
       }    
    }
    
  • Add this attribute on your controller class

    [EnableCors(origins: "*", headers: "*", methods: "*")]
    public class MyController : ApiController
    {  
        [AcceptVerbs("POST")]
        public IHttpActionResult Post([FromBody]YourDataType data)
        {
             ...
             return Ok(result);
        }
    }
    
  • I was able to send json to the action by this way

    $http({
            method: 'POST',
            data: JSON.stringify(data),
            url: 'actionurl',
            headers: {
                'Content-Type': 'application/json; charset=UTF-8'
            }
        }).then(...)
    

Reference : Enabling Cross-Origin Requests in ASP.NET Web API 2

asfez
  • 174
  • 8
2

Somehow I fixed it by changing

<add name="Access-Control-Allow-Headers" 
     value="Origin, X-Requested-With, Content-Type, Accept, Authorization" 
     />

to

<add name="Access-Control-Allow-Headers" 
     value="Origin, Content-Type, Accept, Authorization" 
     />
slugster
  • 47,434
  • 13
  • 92
  • 138
0

Perfectly described in pkozlowski's comment. I had working solution with AngularJS 1.2.6 and ASP.NET Web Api but when I had upgraded AngularJS to 1.3.3 then requests failed.

  • Solution for Web Api server was to add handling of the OPTIONS requests at the beginning of configuration method (more info in this blog post):

    app.Use(async (context, next) =>
    {
        IOwinRequest req = context.Request;
        IOwinResponse res = context.Response;
        if (req.Path.StartsWithSegments(new PathString("/Token")))
        {
            var origin = req.Headers.Get("Origin");
            if (!string.IsNullOrEmpty(origin))
            {
                res.Headers.Set("Access-Control-Allow-Origin", origin);
            }
            if (req.Method == "OPTIONS")
            {
                res.StatusCode = 200;
                res.Headers.AppendCommaSeparatedValues("Access-Control-Allow-Methods", "GET", "POST");
                res.Headers.AppendCommaSeparatedValues("Access-Control-Allow-Headers", "authorization", "content-type");
                return;
            }
        }
        await next();
    });
    
alexwlchan
  • 4,580
  • 5
  • 31
  • 44
  • The above doesnt work for me. There is a much simpler solution to enable CORS for use with AngularJS with ASP.NET WEB API 2.2 and later. Get the Microsoft WebAPI CORS package from Nuget then in your WebAPI config file... var cors = new EnableCorsAttribute("www.example.com", "*", "*"); config.EnableCors(cors); Details are on Microsoft site here http://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api – kmcnamee Apr 16 '15 at 18:22
0

If you are using Jersey for REST API's you can do as below

You don't have to change your webservices implementation.

I will explain for Jersey 2.x

1) First add a ResponseFilter as shown below

import java.io.IOException;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;

public class CorsResponseFilter implements ContainerResponseFilter {

@Override
public void filter(ContainerRequestContext requestContext,   ContainerResponseContext responseContext)
    throws IOException {
        responseContext.getHeaders().add("Access-Control-Allow-Origin","*");
        responseContext.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");

  }
}

2) then in the web.xml , in the jersey servlet declaration add the below

    <init-param>
        <param-name>jersey.config.server.provider.classnames</param-name>
        <param-value>YOUR PACKAGE.CorsResponseFilter</param-value>
    </init-param>
nondescript
  • 1,298
  • 1
  • 11
  • 16
0

I gave up trying to fix this issue.

My IIS web.config had the relevant "Access-Control-Allow-Methods" in it, I experimented adding config settings to my Angular code, but after burning a few hours trying to get Chrome to call a cross-domain JSON web service, I gave up miserably.

In the end, I added a dumb ASP.Net handler webpage, got that to call my JSON web service, and return the results. It was up and running in 2 minutes.

Here's the code I used:

public class LoadJSONData : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        context.Response.ContentType = "text/plain";

        string URL = "......";

        using (var client = new HttpClient())
        {
            // New code:
            client.BaseAddress = new Uri(URL);
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            client.DefaultRequestHeaders.Add("Authorization", "Basic AUTHORIZATION_STRING");

            HttpResponseMessage response = client.GetAsync(URL).Result;
            if (response.IsSuccessStatusCode)
            {
                var content = response.Content.ReadAsStringAsync().Result;
                context.Response.Write("Success: " + content);
            }
            else
            {
                context.Response.Write(response.StatusCode + " : Message - " + response.ReasonPhrase);
            }
        }
    }

    public bool IsReusable
    {
        get
        {
            return false;
        }
    }
}

And in my Angular controller...

$http.get("/Handlers/LoadJSONData.ashx")
   .success(function (data) {
      ....
   });

I'm sure there's a simpler/more generic way of doing this, but life's too short...

This worked for me, and I can get on with doing normal work now !!

Mike Gledhill
  • 23,658
  • 6
  • 133
  • 143
0

For an IIS MVC 5 / Angular CLI ( Yes, I am well aware your problem is with Angular JS ) project with API I did the following:

web.config under <system.webServer> node

    <staticContent>
      <remove fileExtension=".woff2" />
      <mimeMap fileExtension=".woff2" mimeType="font/woff2" />
    </staticContent>
    <httpProtocol>
      <customHeaders>
        <clear />
        <add name="Access-Control-Allow-Origin" value="*" />
        <add name="Access-Control-Allow-Headers" value="Content-Type, atv2" />
        <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS"/>
      </customHeaders>
    </httpProtocol>

global.asax.cs

protected void Application_BeginRequest() {
  if (Request.Headers.AllKeys.Contains("Origin", StringComparer.OrdinalIgnoreCase) && Request.HttpMethod == "OPTIONS") {
    Response.Flush();
    Response.End();
  }
}

That should fix your issues for both MVC and WebAPI without having to do all the other run around. I then created an HttpInterceptor in the Angular CLI project that automatically added in the the relevant header information. Hope this helps someone out in a similar situation.

Damon Drake
  • 809
  • 1
  • 10
  • 23
0

Little late to the party,

If you are using Angular 7 (or 5/6/7) and PHP as the API and still getting this error, try adding following header options to the end point (PHP API).

 header("Access-Control-Allow-Origin: *");
 header("Access-Control-Allow-Methods: PUT, GET, POST, PUT, OPTIONS, DELETE, PATCH");
 header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Authorization");

Note : What only requires is Access-Control-Allow-Methods. But, I am pasting here other two Access-Control-Allow-Origin and Access-Control-Allow-Headers, simply because you will need all of these to be properly set in order Angular App to properly talk to your API.

Hope this helps someone.

Cheers.

Anjana Silva
  • 5,175
  • 3
  • 40
  • 45