CakePHP 3 REST API + CORS Request and OPTIONS method

Narendra Vaghela picture Narendra Vaghela · Sep 7, 2016 · Viewed 7.9k times · Source

I am working on a REST API using CakePHP 3. I want to enable it publicly, so anyone can mane a call to API. So, I have added cors headers as defined here: http://book.cakephp.org/3.0/en/controllers/request-response.html#setting-cross-origin-request-headers-cors

I have implemented the EventListener on Dispatcher.beforeDispatch and Dispatcher.beforeDispatch to prepare the cors headers.

class ApiResponseHeaders implements EventListenerInterface
{

    /**
     * Event bindings
     *
     * @return array
     */
    public function implementedEvents()
    {
        return [
            'Dispatcher.beforeDispatch' => [
                'callable' => 'beforeDispatch',
                'priority' => 0
            ],
            'Dispatcher.afterDispatch' => [
                'callable' => 'afterDispatch',
                'priority' => 99999
            ]
        ];
    }

    public function beforeDispatch(Event $event)
    {
        $request = $event->data['request'];
        if ('OPTIONS' === $request->method()) {
            $event->stopPropagation();
        }
    }

    public function afterDispatch(Event $event)
    {
        $request = $event->data['request'];
        $response = $event->data['response'];

        $response->cors($request)
                ->allowOrigin('*')
                ->allowMethods(['GET', 'POST', 'OPTIONS'])
                ->allowHeaders(['Content-Type, Authorization, X-Requested-With, Accept'])
                ->allowCredentials()
                ->maxAge(0)
                ->build();

        if ('OPTIONS' === $request->method()) {
            $event->stopPropagation();
        }
    }

}

But the problem is, when I make an POST request to API endpoint from AngularJS, first it makes OPTIONS method call and then POST method. CakePHP does not process the OPTIONS method call and returns: 400 Bad Request

Below are the sample request and response headers:

Request headers

OPTIONS /v1/account/login HTTP/1.1
Host: api.cake.dev
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:48.0) Gecko/20100101 Firefox/48.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
Origin: http://demo.angular.dev
Connection: keep-alive

Response headers

HTTP/1.1 400 Bad Request
Date: Wed, 07 Sep 2016 08:34:55 GMT
Server: Apache/2.4.17 (Win64) PHP/5.6.16
X-Powered-By: PHP/5.6.16
X-DEBUGKIT-ID: b4e5654a-cefb-43b7-bf19-4e8c7ffdb0e0
access-control-allow-origin: *
access-control-allow-methods: GET, POST, OPTIONS
access-control-allow-headers: Content-Type, Authorization, X-Requested-With, Accept
Access-Control-Allow-Credentials: true
access-control-max-age: 0
Content-Length: 147
Connection: close
Content-Type: text/html; charset=UTF-8

Is there a way available in CakePHP 3 to handle this OPTIONS request and return correct response so than the next POST request work correctly?

Please help. Thank you

Answer

João Paulo picture João Paulo · Mar 5, 2018

With CakePHP 3.2+, I did in this way:

public function beforeRender(event $event) {
    $this->setCorsHeaders();
}

public function beforeFilter(event $event) {
    if ($this->request->is('options')) {
        $this->setCorsHeaders();
        return $this->response;
    }
}

private function setCorsHeaders() {
    $this->response = $this->response->cors($this->request)
        ->allowOrigin(['*'])
        ->allowMethods(['*'])
        ->allowHeaders(['x-xsrf-token', 'Origin', 'Content-Type', 'X-Auth-Token'])
        ->allowCredentials(['true'])
        ->exposeHeaders(['Link'])
        ->maxAge(300)
        ->build();
}

In this way, I can even handle CakePHP error pages.

Depending on your case, you can change values on allowHeaders array.

Remembering that if the request has withCredentials set to true, you will need to specify the origins.