6

I've recently moved our servers from Rackspace CloudSites (running on Apache/Linux) to Windows Azure Websites. Since the migration, all the jQuery AJAX requests on our REST API have started failing due to CORS.

We use custom headers, so jQuery makes a Pre-flight HTTP OPTIONS request before running the actual API calls. The problem is that the OPTIONS request doesn't seem to reach my PHP code and is instead returned by some other entity (obviously the Web Server) which I seem to have no control over.

I have been using the following headers for a couple of years now so I'm pretty sure the problem isn't in the PHP code:

<?php
    $this->output->set_header("Access-Control-Allow-Origin: *");
    $this->output->set_header("Access-Control-Allow-Methods: GET,POST,DELETE,HEAD,PUT,OPTIONS");
    $this->output->set_header("Access-Control-Allow-Headers: X-Olaround-Debug-Mode, Authorization, Accept");
    $this->output->set_header("Access-Control-Expose-Headers: X-Olaround-Debug-Mode, X-Olaround-Request-Start-Timestamp, X-Olaround-Request-End-Timestamp, X-Olaround-Request-Time, X-Olaround-Request-Method, X-Olaround-Request-Result, X-Olaround-Request-Endpoint" );
?>

I'm guessing the problem is specific to Azure Websites since the code seems to working fine on my development machine (Windows 8 / IIS 8.0) as well. I'm new to Azure (and Windows based hosting in general) so I have almost no clue on how to approach and debug this issue since Azure Websites allow very minimal control.

Uzair Sajid
  • 2,046
  • 2
  • 19
  • 28
  • Is the OPTIONS request being returned with a 405 status code? – Ray Nicholus Sep 24 '13 at 14:07
  • @RayNicholus I actually get an HTTP 404 Not Found with the following response message in the Networks tab of Chrome's developer console: "No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://olman-dev.azurewebsites.net' is therefore not allowed access." – Uzair Sajid Sep 25 '13 at 06:05

3 Answers3

15

I decided to post a complete solution to this problem since the answers already provided (while technically correct) don't work in this particular case for me. The trick was to do the following:

1. Add <customHeaders> in <httpProtocol> in web.config

Like @hcoat also suggested above, adding system.webServer.httpProtocol.customHeaders was the first step to resolve the issue (I had already tried this on my own before, but it didn't work). Add all the custom headers and HTTP methods you need to set for CORS here.

<httpProtocol>
    <customHeaders>
        <add name="Access-Control-Allow-Origin" value="*" />
        <add name="Access-Control-Allow-Methods" value="GET,POST,DELETE,HEAD,PUT,OPTIONS" />
        <add name="Access-Control-Allow-Headers" value="Origin, X-Olaround-Debug-Mode, Authorization, Accept" />
        <add name="Access-Control-Expose-Headers" value="X-Olaround-Debug-Mode, X-Olaround-Request-Start-Timestamp, X-Olaround-Request-End-Timestamp, X-Olaround-Request-Time, X-Olaround-Request-Method, X-Olaround-Request-Result, X-Olaround-Request-Endpoint" />
    </customHeaders>
</httpProtocol>

2. Override the default handler for PHP and remove OPTIONSVerbHandler

Next step (the solution provided by @Bing Han), is to remove the default OPTIONSVerbHandler defined in IIS, and also set a custom PHP54_via_FastCGI handler which accepts your additional HTTP Methods. The default handler only works with GET, POST and HEAD requests.

<handlers>
    <remove name="OPTIONSVerbHandler" />
    <remove name="PHP54_via_FastCGI" />
    <add name="PHP54_via_FastCGI" path="*.php" verb="GET, PUT, POST, DELETE, HEAD, OPTIONS, TRACE, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK" modules="FastCgiModule" scriptProcessor="D:\Program Files (x86)\PHP\v5.4\php-cgi.exe" resourceType="Either" requireAccess="Script" />
</handlers>

Take a look at this post for more details on the inner workings.

3. Remove all the response headers set through your application code

This was the final piece of the puzzle that was causing the most problems. Since IIS was already adding <customHeaders>, the PHP code snippet I shared in the question above was duplicating them. This caused problems at the browser level which didn't respond well to multiple headers of the same type.

The final web.config that worked for this problem

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <rewrite>
            <rules>
                <rule name="Imported Rule 1" stopProcessing="true">
                    <match url="^(.*)$" ignoreCase="false" />
                    <conditions logicalGrouping="MatchAll">
                        <add input="{R:1}" pattern="^(dir_path\.php|lolaround|lolaround\.php|app_assets)" ignoreCase="false" negate="true" />
                    </conditions>
                    <action type="Rewrite" url="lolaround.php/{R:1}" />
                </rule>
                <rule name="Imported Rule 2" stopProcessing="true">
                    <match url="lolaround/(.*)" ignoreCase="false" />
                    <action type="Rewrite" url="/lolaround.php/{R:1}" />
                </rule>
            </rules>
        </rewrite>
        <httpProtocol>
            <customHeaders>
                <add name="Access-Control-Allow-Origin" value="*" />
                <add name="Access-Control-Allow-Methods" value="GET,POST,DELETE,HEAD,PUT,OPTIONS" />
                <add name="Access-Control-Allow-Headers" value="Origin, X-Olaround-Debug-Mode, Authorization, Accept" />
                <add name="Access-Control-Expose-Headers" value="X-Olaround-Debug-Mode, X-Olaround-Request-Start-Timestamp, X-Olaround-Request-End-Timestamp, X-Olaround-Request-Time, X-Olaround-Request-Method, X-Olaround-Request-Result, X-Olaround-Request-Endpoint" />
            </customHeaders>
        </httpProtocol>
        <handlers>
            <remove name="OPTIONSVerbHandler" />
            <remove name="PHP54_via_FastCGI" />
            <add name="PHP54_via_FastCGI" path="*.php" verb="GET, PUT, POST, HEAD, OPTIONS, TRACE, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK" modules="FastCgiModule" scriptProcessor="D:\Program Files (x86)\PHP\v5.4\php-cgi.exe" resourceType="Either" requireAccess="Script" />
        </handlers>
    </system.webServer>
</configuration>

Note: While both @hcoat and @Bing Han's answers were useful in this problem, I can only award the bounty to one of them. I've decided to give it to @Bing Han because his answer got me closest to the solution (and I wasn't able to find a way to add a custom PHP handler from own searching).

Update: I've edited the answer to add support for HTTP DELETE method as well which was missing in the original answer.

Community
  • 1
  • 1
Uzair Sajid
  • 2,046
  • 2
  • 19
  • 28
  • I Accidentally made the edit without logging in first. Oh well :) – Uzair Sajid Oct 14 '13 at 13:08
  • Thanks for your detailed answer, but unfortunately it didn't worked for me (I'm using IIS 7.5). After 2 days of research, the only working solution I found to avoid "405 Method Not Allowed" was to define the CORS headers in the `Application_BeginRequest` method, as mentioned in this answer http://stackoverflow.com/a/14631068/827168 – pomeh Oct 15 '14 at 08:47
  • @pomeh Actually, the question title doesn't make it very clear, but this solution is actually specific to running PHP on Azure Websites. It wouldn't have worked with ASP.NET anyways :) – Uzair Sajid Oct 15 '14 at 10:32
  • 1
    you're right, this solution is related to PHP but points 1 and 3 are language-agnostic. Also, this question is the first result in Google search "azure cors", so I wanted to share a solution I've found that worked for me, to help other people ! :) To be exhaustive, here's a list of possible alternative solutions: http://stackoverflow.com/a/13229373/827168 http://stackoverflow.com/a/15599025/827168 http://stackoverflow.com/a/20705500/827168 http://stackoverflow.com/a/15619435/827168. But IMO the best answer is "use Failed Request Tracing" http://stackoverflow.com/a/6223774/827168 – pomeh Oct 15 '14 at 11:35
  • 1
    You forgot `DELETE` in the final web.config example. Additional note for those who came here much later: 54 and 5.4 should be replaced with 56 and 5.6 or any other PHP version you are using with your IIS PHP web app. – JustAMartin Feb 18 '16 at 08:46
7

HTTP OPTIONS request fails because the default PHP-CGI handler does not handle the "OPTIONS" verb.

Add the following code in web.config file will solve the issue.

<configuration>
  <system.webServer>
    <!-- 
      Some other settings 
    -->
    <handlers>
      <remove name="OPTIONSVerbHandler" />
      <remove name="PHP54_via_FastCGI" />
      <add name="PHP54_via_FastCGI" path="*.php" verb="GET,HEAD,POST,OPTIONS" modules="FastCgiModule" scriptProcessor="D:\Program Files (x86)\PHP\v5.4\php-cgi.exe" resourceType="Either" />
    </handlers>
  </system.webServer>
</configuration>

I have a blog post on this: http://tekblg.blogspot.sg/2013/09/azure-websites-php-cross-domain-request.html

Bing Han
  • 636
  • 6
  • 12
  • I followed the link you shared and did some more research, and technically your solution should work on its own. But in my case, this wasn't enough as well. Turns out that if I add ```customHeaders``` in ```web.config``` (like @hcoat answered) **AND REMOVE** my own from the PHP code, then the API starts to behave normally. I'll add another answer below with the exact code required to make it work. – Uzair Sajid Sep 30 '13 at 09:21
  • Hi @UzairSajid I just tested with custom header, e.g. `header('Access-Control-Expose-Headers: X-Olaround-Debug-Mode, X-Olaround-Request-Start-Timestamp, X-Olaround-Request-End-Timestamp, X-Olaround-Request-Time, X-Olaround-Request-Method, X-Olaround-Request-Result, X-Olaround-Request-Endpoint');`, and was able to get that in the response header. Are you not able to set this through PHP `header`, and only able to set through `web.config`? So far for all my applications, I'm able to set any custom header through `PHP`. Just curious :) – Bing Han Sep 30 '13 at 15:41
  • apparently, only setting the headers in PHP wasn't working. The requests were still being responded to by IIS instead of being handed over to the ```PHP54_via_FastCGI``` handler unless I hard code the `````` in ```web.config```. Once the PHP handler gets control, the PHP headers do work. That's why I was getting duplicates, so I just conditioned out the header code from PHP if it was running on Azure (via an environment variable). – Uzair Sajid Oct 02 '13 at 04:36
4

On a windows server you can not rely on php headers for CORS. You need to create web.config in the site root or application root containing something like the following.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
 <system.webServer>
   <httpProtocol>
     <customHeaders>
       <add name="Access-Control-Allow-Origin" value="*" />
       <add name="Access-Control-Allow-Methods" value="GET,POST,DELETE,HEAD,PUT,OPTIONS" />
       <add name="Access-Control-Allow-Headers" value="X-Olaround-Debug-Mode, Authorization, Accept" />
       <add name="Access-Control-Expose-Headers" value="X-Olaround-Debug-Mode, X-Olaround-Request-Start-Timestamp, X-Olaround-Request-End-Timestamp, X-Olaround-Request-Time, X-Olaround-Request-Method, X-Olaround-Request-Result, X-Olaround-Request-Endpoint" />
     </customHeaders>
   </httpProtocol>
 </system.webServer>
</configuration>

This process is comparable to setting up an .htaccess file, the point being the that you can just create it where you need without reconfiguring the your server.

hcoat
  • 2,463
  • 1
  • 19
  • 27
  • I had already tried this. The problem still persists and IIS throws a 404 despite returning the correct Response Headers. – Uzair Sajid Sep 30 '13 at 05:54