Search code examples
wordpressnginxcorswordpress-rest-api

Access-Control-Allow-Origin server mods are not working


I am having a terrible time trying to get my server to accept requests from another server (local, but given a domain name in my hosts file) without triggering the dreaded

XMLHttpRequest cannot load https://dev.mydomain.org/api/user?uid=1. Origin http://home.domain.org is not allowed by Access-Control-Allow-Origin.

my dev server (internet) is running nginx, my home server(local) is running apache.

I have tried several solutions found on the internet, to no avail. I have tried modifying the headers in the nginx configs to allow my home.mydomain.org server, I have also added htaccess rules locally to allow all origins (*).

My nginx server block has these lines currently:

   add_header Access-Control-Allow-Origin http://home.mydomain.org; 
   add_header Access-Control-Allow-Headers Authorization;

Adding just the first one did change my response slightly (from simple Origin not allowed by Access-Control-Allow-Origin to Request header field Authorization is not allowed by Access-Control-Allow-Headers.) but adding the second line just reverted the error to the original one and I am still blocked.

At this point, I am not sure what else to try.

UPDATES:

  1. Launching Chrome with flag --disable-web-security allows me to test, and my site and code is working fine in Chrome.

  2. However, this revealed another strange problem, which is that if I try adding the add_header lines to a location directive, both my no-web-security Chrome and my unmodified Safari fail to load info from my api. So now I am not sure if my add_header directives in the server block are working correctly at all.

If it helps any, here is my client code (including things I have tried/commented out):

var xhr = new XMLHttpRequest();
var self = this;
xhr.open('GET', apiURL + self.currentIssue);
xhr.setRequestHeader('Access-Control-Allow-Origin','http://home.mydomain.org');
//xhr.setRequestHeader('Access-Control-Allow-Credentials', 'true');
xhr.withCredentials = true;
//xhr.setRequestHeader('Access-Control-Request-Method','*');
xhr.setRequestHeader('Authorization','Bearer longstringoflettersandnumbers');
xhr.onload = function () {
        self.posts = JSON.parse(xhr.responseText);
        };
xhr.send();

ANOTHER UPDATE AFTER TRYING SUGGESTION BELOW: After a bunch of trial and error on both client and server, I still am stuck. Here is my latest response from the server using curl (although I have toggled on and off various options client and server for things like Credentials and changing origin to exactly mine or * to no avail):

HTTP/1.1 204 No Content Server: nginx Date: Sun, 06 Aug 2017 10:11:57 GMT Connection: keep-alive Access-Control-Allow-Origin: http://home.mydomain.org Access-Control-Allow-Credentials: true Access-Control-Allow-Methods: GET, POST, OPTIONS Access-Control-Allow-Headers: Authorization,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range Access-Control-Max-Age: 1728000 Content-Type: text/plain; charset=utf-8 Content-Length: 0

and here are my console errors (Safari):

[Error] Origin http://home.mydomain.org is not allowed by Access-Control-Allow-Origin.
[Error] Failed to load resource: Origin http://home.mydomain.org is not allowed by Access-Control-Allow-Origin. (actions, line 0)
[Error] XMLHttpRequest cannot load https://dev.mydomain.org/api/user?uid=1 due to access control checks.

And here is my console error for Firefox:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://dev.mydomain.org/api/user?uid=1. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).

Also in Firefox, here are the results from the network panel for OPTIONS and GET:

Request URL: https://dev.mydomain.org/api/user?uid=1
Request method: OPTIONS
Status code: 204 No Content
Version: HTTP/2.0

Response headers (511 B)    
Server  "nginx"
Date    "Sun, 06 Aug 2017 10:44:22 GMT"
Access-Control-Allow-Origin "http://home.mydomain.org"
access-control-allow-credentials    "true"
Access-Control-Allow-Methods    "GET, POST, OPTIONS"
Access-Control-Allow-Headers    "Authorization,DNT,X-CustomHea…ent-Type,Content-Range,Range"
Access-Control-Max-Age  "1728000"
Content-Type    "text/plain; charset=utf-8"
Content-Length  "0"
X-Firefox-Spdy  "h2"
Request headers (501 B) 
Host    "dev.mydomain.org"
User-Agent  "Mozilla/5.0 (Macintosh; Intel… Gecko/20100101 Firefox/54.0"
Accept  "text/html,application/xhtml+x…lication/xml;q=0.9,*/*;q=0.8"
Accept-Language "en-US,en;q=0.5"
Accept-Encoding "gzip, deflate, br"
Access-Control-Request-Method   "GET"
Access-Control-Request-Headers  "authorization"
Origin  "http://home.mydomain.org"
Connection  "keep-alive"
Cache-Control   "max-age=0"


Request URL: https://dev.mydomain.org/api/user?uid=1
Request method: GET
Status code: 404 Not Found
Version: HTTP/2.0

Response headers (170 B)    
Server  "nginx"
Date    "Sun, 06 Aug 2017 10:44:22 GMT"
Content-Type    "text/html"
Vary    "Accept-Encoding"
Content-Encoding    "gzip"
X-Firefox-Spdy  "h2"
Request headers (723 B) 
Host    "dev.mydomain.org"
User-Agent  "Mozilla/5.0 (Macintosh; Intel… Gecko/20100101 Firefox/54.0"
Accept  "*/*"
Accept-Language "en-US,en;q=0.5"
Accept-Encoding "gzip, deflate, br"
Referer "http://home.mydomain.org/"
Authorization   "Bearer eyJ0eXAG…BRHmX9VmtYHQOvH7k-Y32wwyeCdk"
Origin  "http://home.mydomain.org"
Connection  "keep-alive"
Cache-Control "max-age=0"

UPDATE WITH PARTIAL SUCESS:

I think I found the problem (partially): changing my location directive in nginx from location /api to location = /api/* gets it working! But only for Safari and Chrome, FF is now not even trying the GET request, there is NO entry for it in network panel.

UPDATE WITH CRYING AND GNASHING OF TEETH AND PULLING OF HAIR Safari and Chrome intermittently fail with original error about Origin not allowed, even though they were working fine and no changes have been made to server config. I will be drinking heavily tonight...


Solution

  • Wow, was that ever convoluted. Posting answer here in case some other WP user finds their way here. I kept getting inconsistent results (sometimes working, sometimes not mysteriously) and finally tracked down my problem to headers being set in the PHP code on the server, independently of the nginx settings and sometimes contradicting them (although never in a predictable way that I could see). So the things I needed to resolve were:

    • Removed all my cors declarations in my nginx configs
    • I also have code on my server that validates a token in the auth header, and it was failing on OPTIONS preflight (which it should never check) so I had to add an if statement before to have it ignore an OPTIONS call (!$_SERVER['REQUEST_METHOD'] === "OPTIONS")
    • Since I had cloned this site from another of mine using UpdraftPlus plugin, I had to go in to delete my migrate keys since their existence prevented api calls from working too. Once they were deleted my calls started working again.
    • Removed and re-added the built in WP filter rest_pre_serve_request

    My filter code is here:

    add_action('rest_api_init', function() {
    /* unhook default function */
    remove_filter('rest_pre_serve_request', 'rest_send_cors_headers');   
    /* then add your own filter */
    add_filter('rest_pre_serve_request', function( $value ) {
    $origin = get_http_origin();
    $my_sites = array( $origin ); // add array of accepted sites if you prefer
    if ( in_array( $origin, $my_sites ) ) {
    header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) );
    } else {
    header( 'Access-Control-Allow-Origin: ' . esc_url_raw( site_url() ) );
    }
    header( 'Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, PATCH, DELETE' );
    header( 'Access-Control-Allow-Credentials: true' );
    header('Access-Control-Allow-Headers: Authorization,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Origin,Content-Type,X-Auth-Token,Content-Range,Range');
    header('Access-Control-Expose-Headers: Authorization,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Origin,Content-Type,X-Auth-Token,Content-Range,Range');
    header( 'Vary: Origin' );
    return $value;
    });
    }, 15);
    

    Now finally, everything works everywhere (in every browser and in curl too)!