CORS: PHP: Response to preflight request doesn't pass. Am allowing origin
Asked Answered
L

5

20

So I know there's a lot of CORS posts out there, and I'm just adding to them, but I can't find any with answers that help me out. So I'm building an angular 4 application that relies on my php api. Working locally it's fine, the moment I toss it up on the domain with the app at app.example.com, and the api at api.example.com, I can't get past my login, because I get the following error:

XMLHttpRequest cannot load http://api.example.com/Account/Login. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://app.example.com' is therefore not allowed access.

My php code looks like this:

$http_origin = $_SERVER['HTTP_ORIGIN'];

$allowed_domains = array(
    'http://example.com',
    'https://example.com',
    'http://app.example.com',
    'https://app.example.com',
    'http://www.example.com',
    'https://www.example.com'
);

if (in_array(strtolower($http_origin), $allowed_domains))
{  
    // header("Access-Control-Allow-Origin: *");
    header("Access-Control-Allow-Origin: $http_origin");
    header('Access-Control-Allow-Credentials: true');
    header('Access-Control-Max-Age: 86400');
}

// Access-Control headers are received during OPTIONS requests
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
        header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
        header("Access-Control-Allow-Headers: Authorization, Content-Type,Accept, Origin");
    exit(0);
}

My Angular post looks like this:

public login(login: Login): Observable<LoginResponse> {
    let headers = new Headers();
    headers.append('Content-Type', 'application/x-www-form-urlencoded');
    headers.append('Authorization', 'Basic ' + btoa(login.Username + ':' + login.Password));
    return  this.http.post(this.apiBaseUrl + '/Account/Login', "grant_type=client_credentials", { headers: headers })
        .map(response => {
            // code
        });
}

If I run the request through postman, which doesn't bother with CORS, I get:

{ "error": "invalid_client", "error_description": "Client credentials were not found in the headers or body" }

I've tried setting origin to '*' just to test and see if that was the core of the issue, and it still fails the same way.

Edit Just updating from information below. Changing casing in headers had no effect, and pulling the code out of their if statements had no effect.

I debugged the php by telling my live app to go to my local api, and the php is working as expected. It's setting the headers and making it into each of the if statements.

Edit take 2 I could really use some help on this one, if someone has any ideas, I'd really appreciate it.

Edit take 3 If I set all the header stuff in my .htaccess rather than my php, it lets me through. However, now I'm stuck on the error listed above that I always get when using postman, however now it's while using the actual site.

{"error":"invalid_client","error_description":"Client credentials were not found in the headers or body"}

My response headers are like so

Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:authorization, content-type, accept, origin
Access-Control-Allow-Methods:GET, POST, OPTIONS
Access-Control-Allow-Origin:*

I'll be changing it from * to only my domains once I have it working. But for now i'll leave it as *.

My headers as requested.

Headers for failed request

Lindbom answered 11/6, 2017 at 1:50 Comment(15)
take out the code from the if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') { and check what happened.Convivial
can you share the snap shot of server response by inspecting network tab in browser ?Convivial
The response tab in the debugger is blank, but here's the headers -- Response: Allow:POST,OPTIONS,GET,HEAD,TRACE Connection:keep-alive Content-Length:0 Content-Type:text/plain Date:Sun, 11 Jun 2017 02:41:43 GMT Keep-Alive:timeout=30 Server:Apache/2 Request: Accept:*/* Accept-Encoding:gzip, deflate Accept-Language:en-US,en;q=0.8,ca;q=0.6 Access-Control-Request-Headers:authorization Access-Control-Request-Method:POST Connection:keep-alive Host:api.example.com Origin:http://app.example.com Referer:http://app.example.com/loginLindbom
@MasoodUrRehman I tried pulling everything out, same result.Lindbom
make the values in lower case of Access-Control-Allow-Headers. I don't know why but i had faced that problem before.Convivial
@MasoodUrRehman No luck there either.Lindbom
What is $http_origin on your server? There's no Access-Control-Allow-Origin on the response headers you posted, so that explains why your browser is kicking up a fuss. It actually doesn't appear that any of your headers are being sent, therefore I don't even think either of those if statements are true. Having a dynamic Access-Control-Allow-Origin based on $_SERVER['http_origin'] is a little strange. You should consider sending the actual domains you accept with each request, or making that an environment setting (to avoid hard coding domains)Morehead
$http_origin ends up being http://app.example.com, and I'm limiting access to those domains because I know exactly the domains I want to be able to access my api. But I have tried just wild carding it (*), with no luck. It's like I don't even get to my code when on the server. When the api call is directed towards my local version of the api, the code runs fine, and since it's a .com address to a localhost address, it triggers all the cors just the same as live.Lindbom
@Lindbom — If the code works across origin when you use a different server, that suggests the problem is not with your code. Perhaps, for example, you have some kind of reverse proxy on the server which strips out of the Access-Control-Allow-Origin header.Stallings
@quentin Holy crap, good call. I'll check that out today. ThanksLindbom
@Stallings - I have updated my question with some new info. Doing stuff in the .htaccess file rather than the php seems to work for the cors.Lindbom
Cam you please include a screenshot of chrome devtools request response headers for your requests? After I would see them I could helpVaughn
@TzookBarNoy I have added the requested screen shot to my question.Lindbom
@Lindbom this is the POST request, if you got to the post request I assume that the OPTIONS request went well... can you please add the OPTIONS request as well, and surely if you 400 on the POST request what is giving the 400 in your app?Vaughn
@TzookBarNoy Oh, I'm sorry, perhaps I should have updated the question more obviously, but setting origin settings on the .htaccess has successfully put me past the preflight request. The existing issue is the client credentials issue.Lindbom
V
25

OK I had a similar issues recently and I solved everything only on the backend side with no .htaccess stuff.

when the browser sends cross server requests it firsts sends an OPTIONS request to make sure it is valid and it can send the "real" request. After it gets a proper and valid response from OPTIONS, only then it sends the "real" request.

Now for both request on the backend you need to make sure to return the proper headers: content-type, allow-origin, allow-headers etc...

Make sure that in the OPTIONS request on the backend, the app returns the headers and returns the response, not continuing the full flow of the app.

In the "real" request, you should return the proper headers and your regular response body.

example:

    //The Response object
    $res = $app->response;

    $res->headers->set('Content-Type', 'application/json');
    $res->headers->set('Access-Control-Allow-Origin', 'http://example.com');
    $res->headers->set('Access-Control-Allow-Credentials', 'true');
    $res->headers->set('Access-Control-Max-Age', '60');
    $res->headers->set('Access-Control-Allow-Headers', 'AccountKey,x-requested-with, Content-Type, origin, authorization, accept, client-security-token, host, date, cookie, cookie2');
    $res->headers->set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');

    if ( ! $req->isOptions()) {
        // this continues the normal flow of the app, and will return the proper body
        $this->next->call();
    } else {
        //stops the app, and sends the response
        return $res;
    }

Things to remember:

  • if you are using: "Access-Control-Allow-Credentials" = true make sure that "Access-Control-Allow-Origin" is not "*", it must be set with a proper domain! ( a lot of blood was spilled here :/ )

  • define the allowed headers you will get in "Access-Control-Allow-Headers" if you wont define them, the request will fail

  • if you using "Authorization: Bearer", then "Access-Control-Allow-Headers" should also contain "Authorization", if not, the request will fail
Vaughn answered 20/6, 2017 at 21:35 Comment(7)
I can't test this yet, but it sounds right. I'm going to go ahead and accept the answer so you can get the bounty before it expires. Thanks!Lindbom
Thanks :) if it doesn't work, please comment till we solve it. I wasted so much time on that just a week ago, so I relate to you hahaVaughn
would you mind taking a look at a related issue for me? #44751829Lindbom
It works perfect! I was looking for this explanation and Access-Control-Allow-Headers to solve my CORS policy problem.Coltson
Thanks: especially for the remark 'Things to remember'. Addition: If you use the header Authorization, it forses a preflight request. The header 'Access-Control-Allow-Headers' should also contains 'Authorization'.Ceric
I am using axios and php, when i added headers and try to sent Authorization from axios. My sql query written on same executed 2 times. Dont understand issueMosira
Note that it looks like firefox don't like wildcard '*' value for Access-Control related headers (at least Allow-Methods ans Allow-Headers) despite mozilla doc says it is Firefox for android only).Schumann
D
15

In my similar case with Angular frontend and Php backend helped code below. Firstly I send a headers:

header("Access-Control-Allow-Origin: http://localhost:4200");   
header("Content-Type: application/json; charset=UTF-8");    
header("Access-Control-Allow-Methods: POST, DELETE, OPTIONS");    
header("Access-Control-Max-Age: 3600");    
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");    

And after them I'm enable ignoring the options request:

if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {    
   return 0;    
}    

This approach helped me handling the "post" and the "delete" embedded request methods from the Angular.

Diadiabase answered 17/5, 2020 at 18:37 Comment(0)
S
2

I added below in php and it solved my problem.

header("Access-Control-Allow-Origin: *");

header("Access-Control-Allow-Headers: Content-Type, origin");
Sail answered 14/10, 2017 at 19:53 Comment(3)
where exactly is this code supposed to be ? some more details and explanation would help beginners like meCorenda
@AbdealiChandanwala Just before whatever you're returningVivyanne
can we make a php file like connection.php and put these lines into it and use thaat file into different php files (where we are returning response) ?Mosira
R
1

I had a similar issue,

Dev environment: Apache web server behind NginX Proxy

My app is in a virtual host in my Apache server, configured with name: appname.devdomain.com

When accessing to web app internaly I wasn´t getting through the proxy: I was using the url: appname.devdomain.com

I had no problem this way.

But, when accessing it externally using public url: appname.prddomain.com it would load, even got access to the system after login, then load the templates and some session content, then, if an asynchronous call would be made by the client then I would got the following message in chrome console:

"Access to XMLHttpRequest at 'http://appname.devdomain.com/miAdminPanel.php' from origin 'http://appname.prddomain.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request."

To test this I opened two tabs

1st tab accessed the web using url: appname.devdomain.com, made XMLHttpRequest -> OK

2nd tab accessed the web using url: appname.prddomain.com, made XMLHttpRequest -> CORS error message above.

So, after changing the Nginx proxy configuration:

server {
    listen      80;
    server_name appname.prddomain.com;

    # SSL log files ###
    access_log      /path/to/acces-log.log;
    error_log       /path/to/error-log.log;

location / {
    proxy_pass  http://appname.devdomain.com;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

basically this tells the proxy to treat the external request as internal request.

Roots answered 29/3, 2019 at 14:26 Comment(0)
B
0

We need to reply which methods our server can process without any response. Just add it at the top of your php and everything will work:

if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
    header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE');
    header("Access-Control-Allow-Headers: $_SERVER[HTTP_ACCESS_CONTROL_REQUEST_HEADERS]");
    header("Content-Length: 0");
    header("Content-Type: text/plain");
    exit;
}

https://www.rfc-editor.org/rfc/rfc9110.html#section-9.3.7

9.3.7. OPTIONS The OPTIONS method requests information about the communication options available for the target resource, at either the origin server or an intervening intermediary. This method allows a client to determine the options and/or requirements associated with a resource, or the capabilities of a server, without implying a resource action.

Blastomere answered 26/4 at 16:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.