evan108108 / RESTFullYii

RESTFull API for your Yii application

Home Page:http://evan108108.github.com/RESTFullYii/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

CORS not working

kaushikkapil opened this issue · comments

@evan108108

The issue I am mentioning below is similar to another issue highlighted earlier -- #68

I have done the required changes in restfullyii, i.e.

        //REST CORS support
         $this->onRest('req.cors.access.control.allow.origin', function() {
            return [Yii::app()->params['cors_url']]; //List of sites allowed to make CORS requests 
        });

        $this->onRest('req.cors.access.control.allow.methods', function() {
            return ['GET', 'POST', 'PUT', 'DELETE']; //List of allowed http methods (verbs) 
        });

and then I am trying to make the CORS request from my angularjs frontend:

$http.defaults.headers.post = {"X_REST_CORS" : "Yes"};
authService.login = function (credentials) {
      return $http
        .post('http://cv.yii/api/admin/login/user/', credentials)
        .then(function (res) {
          Session.create(res.id, res.user.id, res.user.role);
          return res.user;
        },

As you can see, I have explicitly added the "X_REST_CORS" and have made a "POST" request, but for some reason, the code doesn't work. The network call stack shows that it breaks down while making an "OPTIONS request" to the server code.

OPTIONS http://cv.yii/api/admin/login/user/ 500 (Internal Server Error) angular.js:8521
(anonymous function) angular.js:8521
sendReq angular.js:8315
$http.serverRequest angular.js:8049
wrappedCallback angular.js:11520
wrappedCallback angular.js:11520
(anonymous function) angular.js:11606
Scope.$eval angular.js:12632
Scope.$digest angular.js:12444
Scope.$apply angular.js:12736
(anonymous function) angular.js:19035
n.event.dispatch jquery.min.js:3
r.handle

XMLHttpRequest cannot load http://cv.yii/api/admin/login/user/. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://fontend.url' is therefore not allowed access. 

Since I don't have any other info here, I am hitting a deadend on how to solve this problem. Your inputs would be very much helpful.

Thanks !

Some users have reported this. I can not replicate it. However, there is a work around:

issue-122

I am still having this problem. 

I have to send manually "header('Access-Control-Allow-Origin: http://hostname');"

from inside "post.filter.req.auth.cors" in order to have CORS to work. 

I am not sure where this issue comes from...

If you solve it please send a pull request!

@kaushikkapil :> Were you able to get this working?

@evan108108
No...its still not working for me.....though for a different reason now.

I did the following settings as suggested in this link -- http://thibaultdenizet.com/tutorial/cors-with-angular-js-and-sinatra/

//Enable cross domain calls
$httpProvider.defaults.useXDomain = true;
//Remove the header containing XMLHttpRequest used to identify ajax call 
//that would prevent CORS from working
delete $httpProvider.defaults.headers.common['X-Requested-With'];

But i was still getting the error. Then I realized that I needed to use "X-REST-CORS" instead of "X_REST_CORS"

Once I did the change, then I did not get the CORS error that I was getting earlier. However, the problem I am now facing is that when I try to make the POST or GET request, I see that an "OPTIONS" request happens instead, for which I get a "302 found" response, and then after that nothing happens, i.e. I the POST/GET request is not executed at all.

Adding "header('Access-Control-Allow-Origin: http://hostname');" in the "post.filter.req.auth.cors" section does not seem to make any difference.

@evan108108 :
I tried further debugging on this with the "Advanced Rest Client" extension in chrome. In that, I gave the following inputs:

image

In the output of this, I see that the OPTIONS request is getting redirected to the auth page. I think this explains the "302 found" I was getting earlier, but I am not clear as to why it is getting redirected, and how can I prevent it from happening.

Redirect #1
To:http://cv.yii/index.php/user/user/login with status: 302 Show explanation HTTP/1.1 302 Found
Redirection information has not been cached.
Date: Thu, 21 Aug 2014 12:47:59 GMT 
Server: Apache/2.4.7 (Ubuntu) 
X-Powered-By: PHP/5.5.9-1ubuntu4.1
Expires: Thu, 19 Nov 1981 08:52:00 GMT 
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0 
Pragma: no-cache 
Location: http://cv.yii/index.php/user/user/login 
Content-Length: 1 
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html 

Well RESTFullYii does not redirect. That means your app/controller is
redirecting. That likely means that your action permissions are not
configured properly.

On Thursday, August 21, 2014, Kapil Kaushik notifications@github.com
wrote:

@evan108108 https://github.com/evan108108 :
I tried further debugging on this with the "Advanced Rest Client"
extension in chrome. In that, I gave the following inputs:

[image: image]
https://cloud.githubusercontent.com/assets/1407928/3996183/70273736-2932-11e4-8514-4a3eb7211447.png

In the output of this, I see that the OPTIONS request is getting
redirected to the auth page. I think this explains the "302 found" I was
getting earlier, but I am not clear as to why it is getting redirected, and
how can I prevent it from happening.

Redirect #1
To:http://cv.yii/index.php/user/user/login with status: 302 Show explanation HTTP/1.1 302 Found
Redirection information has not been cached.
Date: Thu, 21 Aug 2014 12:47:59 GMT
Server: Apache/2.4.7 (Ubuntu)
X-Powered-By: PHP/5.5.9-1ubuntu4.1
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Location: http://cv.yii/index.php/user/user/login
Content-Length: 1
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html


Reply to this email directly or view it on GitHub
#152 (comment)
.

Fair point @evan108108. However, I think the problem here is that the "OPTIONS" request is not being handled by RESTFullYii and so it gets passed to the default Yii framework and consequently gets denied.

If we see the suggested filters function in the documentation

    public function filters()
    {
        return array(
            'accessControl', // perform access control for CRUD operations
            array(
                'RestfullYii.filters.ERestFilter + 
                REST.GET, REST.PUT, REST.POST, REST.DELETE'
            ),
        );
    }

Here we see that RestfullYii applies accesscontrol only for "POST/PUT/GET/DELETE" requests. Now if the client ends up making an OPTIONS request (as in the current case), then I would expect that it would bypass the RESTFullYii flow and go to default Yii flow, which might be causing the default redirect. Since the "api" routes are handled by RESTFullYii, I do not see where I can handle it in my default controller, unless we handle it with RESTFullYii itself. I tried to do that by adding the "REST.OPTIONS" to the filter list, but it did not work.

What are your thoughts on this? Do you think we could sync up sometime over skype or teamviewer to debug this together?

@kaushikkapil :> Listen, this is not an Bug, its a simple config issue. If you are being redirected, then it was not RESTFullYii. RESTFullYii simply does not redirect. Thus, either you do not have your routes configured properly or you do not have your controller configured properly.

Make sure that your controller has options set for both "filters" & "accessRules"

    /**
     * @return array action filters
     */
    public function filters()
    {
        return array(
            'accessControl', // perform access control for CRUD operations
            'postOnly + delete', // we only allow deletion via POST request
             array('RestfullYii.filters.ERestFilter + REST.GET, REST.PUT, REST.POST, REST.DELETE, REST.OPTIONS'),
        );
    }

    public function actions()
    {
        return array(
            'REST.'=>'RestfullYii.actions.ERestActionProvider',
        );
    }

    /**
     * Specifies the access control rules.
     * This method is used by the 'accessControl' filter.
     * @return array access control rules
     */
    public function accessRules()
    {
        return array(
            array('allow', 
                'actions'=>array('REST.GET', 'REST.PUT', 'REST.POST', 'REST.DELETE', 'REST.OPTIONS'),
                'users'=>array('*'),
            ),
            ...
            array('deny',  // deny all users
                'users'=>array('*'),
            ),
        );
    }

@evan108108
I was finally able to solve the issue. There were multiple things that needed to be in place. I mention them below.

  1. I had added "REST.OPTIONS" only in filters and not in "accesscontrol". This was the cause of the redirect issue. However, fixing this did not address the issue.
  2. I was able to get it working by adding the following code...thanks to this link -- http://remysharp.com/2011/04/21/getting-cors-working/
        // respond to preflights
        if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
            echo "request type -- options\n";
            // return only the headers and not the content
            if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) {
                header('Access-Control-Allow-Origin: '.Yii::app()->params['cors_url']);
                header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
                header('Access-Control-Allow-Headers: Origin, X-Requested-With, X-REST-CORS, Content-Type, Accept');
            }
            exit;
        }

The reason being that in CORS, a pre-flight OPTIONS request was being made which in its return value expects:
-- the allowed headers (if I don't return 'X-REST-CORS' here, then the CORS request gets rejected)
-- the allowed methods
-- the allowed domains

Till I added this code, it would not work for me, even though I did all the other config changes as suggested in the RESTFullYii documentation.

Thanks a lot for your help, the redirect thing was a real pain as it kept shielding the real issue !

I want to add this because is really helpfull:
after trying for several days how to avoid my application trigger OPTIONS I found a very usefull method using rewrites apache engine..
The trick is forcing the webserver to respond with status 200, so in nginx is very simple but with apache I had to combine configuration and .htaccess rewrites rules:

my apache config:
Header set Access-Control-Allow-Origin "*"
Header set Access-Control-Allow-Methods "GET, POST, PUT, HEAD, OPTIONS"
Header set Access-Control-Allow-Headers "X-REST-CORS,X-Requested-With,Content-Type"

My .htaccess rewrites:
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ blank.html [QSA,L]

This worked for me, now I'm successfully posting data to my API. now, I just have to understand how the rest of behaviors works because honnestly, I don't have a clue.

regards