5

I built an API with the SLIM Micro-Framework. I setup some middleware that adds the CORS headers using the following code.

class Cors{

    public function __invoke(Request $request, Response $response, $next){

        $response = $next($request, $response);
        return $response
            ->withHeader('Access-Control-Allow-Origin', 'http://mysite')
            ->withHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept, Origin, Authorization')
            ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
    }

}

For my front-end, I used VueJS. I setup VueResource and created a function with the following code.

register (context, email, password) {
  Vue.http({
    url: 'api/auth/register',
    method: 'POST',
    data: {
    email: email,
    password: password
  }
}).then(response => {
  context.success = true
}, response => {
  context.response = response.data
  context.error = true
})
}

In chrome, the following error is logged to the console.

XMLHttpRequest cannot load http://mysite:9800/api/auth/register. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://mysite' is therefore not allowed access.

Oddly enough, GET requests work perfectly.

Jerome Carter
  • 237
  • 1
  • 4
  • 18

4 Answers4

2

You half 1/2 the solution here.

What you are missing is an OPTIONS route where these headers need to be added as well.

$app->options('/{routes:.+}', function ($request, $response, $args) {
    return $response
        ->withHeader('Access-Control-Allow-Origin', 'http://mysite')
        ->withHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept, Origin, Authorization')
        ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
});
geggleto
  • 2,605
  • 1
  • 15
  • 18
  • 1
    Thank you!!! Great answer, buddy. It works perfectly now. I couldn't have done it without you. – Jerome Carter Jun 26 '16 at 20:52
  • @geggleto As per my knowledge, I have found that for CORS, browser clients have a mandate to make a pre-flight request of OPTIONS and they validate the response headers that you stated above. Does it means that disabling of OPTIONS and not setting those headers will lead to failure? And we do have to `return response.send(200);` everytime for such requests? – xploreraj Sep 06 '16 at 09:38
1

This happens because preflight request is of OPTIONS type. You need to make an event listener on your request, which checks the type and sends a response with needed headers.

Unfortunately i don't know Slim framework, but here's the working example in Symfony.

First the headers example to be returned:

// Headers allowed to be returned.
const ALLOWED_HEADERS = ['Authorization', 'Origin', 'Content-Type', 'Content-Length', 'Accept'];

And in the request listener, there's a onKernelRequest method that watches all requests that are coming in:

    /**
     * @param GetResponseEvent $event
     */
    public function onKernelRequest(GetResponseEvent $event)
    {
        // Don't do anything if it's not the master request
        if (!$event->isMasterRequest()) {
            return;
        }

        // Catch all pre-request events
        if ($event->getRequest()->isMethod('OPTIONS')) {
            $router = $this->container->get('router');
            $pathInfo = $event->getRequest()->getPathInfo();

            $response = new Response();
            $response->headers->set('Access-Control-Allow-Origin', $event->getRequest()->headers->get('Origin'));
            $response->headers->set('Access-Control-Allow-Methods', $this->getAllowedMethods($router, $pathInfo));
            $response->headers->set('Access-Control-Allow-Headers', implode(', ', self::ALLOWED_HEADERS));
            $response->headers->set('Access-Control-Expose-Headers', implode(', ', self::ALLOWED_HEADERS));
            $response->headers->set('Access-Control-Allow-Credentials', 'true');
            $response->headers->set('Access-Control-Max-Age', 60 * 60 * 24);
            $response->send();
        }
    }

Here i just reproduce the Origin (all domains are allowed to request the resource, you should probably change it to your domain). Hope it will give some glues.

Andrii
  • 329
  • 2
  • 8
1

Actually CORS is implemented at browser level. and Even with

 return $response
            ->withHeader('Access-Control-Allow-Origin', 'http://mysite')
            ->withHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept, Origin, Authorization')
            ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');

chrome and Mozilla will not set headers to allow cross origin. So, you need forcefully disable that..

Read more about disabling CORS

Disable same origin policy in Chrome

Community
  • 1
  • 1
Atul Sharma
  • 9,397
  • 10
  • 38
  • 65
  • More on this answer... In order to deal with the pre-flight require you need to accept OPTIONS in addition to the actual HTTP Verb. – geggleto Jun 24 '16 at 13:14
  • Care to further explain? @geggleto – Jerome Carter Jun 25 '16 at 00:50
  • I want to use it in production, though – Jerome Carter Jun 25 '16 at 02:40
  • In production this will work as both are on same domain. – Atul Sharma Jun 25 '16 at 12:08
  • http://www.slimframework.com/docs/cookbook/enable-cors.html Take a look here @JeromeCarter – geggleto Sep 06 '16 at 14:46
  • Disabling same-origin policy in browser gives many perpetrators on the web to execute malicious scripts to read personal data from various websites, thus you should never disable it in your browser just because CORS doesn't work in your application. This is not a good answer, and those who follow it will be putting their browser & information at risk. – user482594 Jun 27 '21 at 12:14
1

CORS can be hard to config. The key is that you need to set the special headers in your server and your client, and I don't see any Vue headers set, besides as far as I know http is not a function. However here is some setup for a post request.

const data = {
    email: email,
    password: password
  } 
const options = {
    headers: {
        'Access-Control-Expose-Headers': // all of your headers,
        'Access-Control-Allow-Origin': '*'
    }
}
Vue.http.post('api/auth/register', JSON.stringify(data), options).then(response => {
    // success
}, response => {
    // error
})

Notice that you need to stringify your data and you need to expose your headers, usually including the Access-Control-Allow-Origin header. What I did in one of my own apps was to define interceptors so I don't worry to set headers for every request.

Vue.http.headers.common['Access-Control-Expose-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept, x-session-token, timeout, Content-Length, location, *'
Vue.http.headers.common['Access-Control-Allow-Origin'] = '*'
Yerko Palma
  • 12,041
  • 5
  • 33
  • 57