Solved thread

This post is marked as solved. If you think the information contained on this thread must be part of the official documentation, please contribute submitting a pull request to its repository.

Enable Cross Origin Resource Sharing

Hello, I'm developing a RESTful API and I need to enable my API to accept request from different domain. So, I need to enable Cross Origin Resource Sharing. Anybody know how can I do that?

Thanks



5.3k
Accepted
answer
edited Jul '14

Hi, I found the solution.

First you need to define some headers to your method:

$response = $app->response;                      
$response->setHeader('Access-Control-Allow-Origin', '*');
$response->setHeader('Access-Control-Allow-Headers', 'X-Requested-With');      
$response->sendHeaders();

And you need to create a method to preflight. Like that:

$app->get('/preflight', function() use ($app) {
        $content_type = 'application/json';
        $status = 200;
        $description = 'OK';
        $response = $app->response;

        $status_header = 'HTTP/1.1 ' . $status . ' ' . $description;
        $response->setRawHeader($status_header);
        $response->setStatusCode($status, $description);
        $response->setContentType($content_type, 'UTF-8');
        $response->setHeader('Access-Control-Allow-Origin', '*');
        $response->setHeader('Access-Control-Allow-Headers', 'X-Requested-With');
        $response->setHeader("Access-Control-Allow-Headers: Authorization");
        $response->setHeader('Content-type: ' . $content_type);
        $response->sendHeaders();
    });

Good to know! - I started googling around for this info but am glad you found it! :)

I have a basic API project that handles this: https://github.com/cmoore4/phalcon-rest

Check out controllers/RESTController.php. See the optionsBase and optionsOne functions.

Then all you need to do is define a route: $app->options('/', 'optionsBase'); See the index.php file for examples.

Preflight is handled via OPTIONS in most browsers.



85
edited Jul '14

Hi guys, here is how to handle it:

$app->before(function() use ($app) {
$origin = $app->request->getHeader("ORIGIN") ? $app->request->getHeader("ORIGIN") : '*';

$app->response->setHeader("Access-Control-Allow-Origin", $origin)
      ->setHeader("Access-Control-Allow-Methods", 'GET,PUT,POST,DELETE,OPTIONS')
      ->setHeader("Access-Control-Allow-Headers", 'Origin, X-Requested-With, Content-Range, Content-Disposition, Content-Type, Authorization')
      ->setHeader("Access-Control-Allow-Credentials", true);
});

$app->options('/{catch:(.*)}', function() use ($app) { 
    $app->response->setStatusCode(200, "OK")->send();
});

The ->before() block is required if your API is consummed by client-side JS (Angular, EmberJS...) and is executed ONLY if a route has been matched, that's why you need the ->options() catch-all block.

@Pierre your method works only with Phalcon\Mvc\Micro because Phalcon\Mvc\Application class doesn't have options() and before() methods.



85

I didn't implement this on the full-stack framework yet, I can't help you more.

I don't need help, it was just a precisation.



144

Just in case it results useful for someone else I publish here my plugin:

use Phalcon\Events\Event;
use Phalcon\Mvc\Micro;
use Phalcon\Mvc\User\Plugin;

class CORSPlugin extends Plugin {

    public function beforeHandleRoute(Event $event, Micro $app) {
        $origin = $app->request->getHeader('ORIGIN') ? $app->request->getHeader('ORIGIN') : '*';

        if (strtoupper($app->request->getMethod()) == 'OPTIONS') {
            $app->response
                ->setHeader('Access-Control-Allow-Origin', $origin)
                ->setHeader('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS')
                ->setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Range, Content-Disposition, Content-Type, Authorization')
                ->setHeader('Access-Control-Allow-Credentials', 'true');

            $app->response->setStatusCode(200, 'OK')->send();

            exit;
        }

        $app->response
            ->setHeader('Access-Control-Allow-Origin', $origin)
            ->setHeader('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS')
            ->setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Range, Content-Disposition, Content-Type, Authorization')
            ->setHeader('Access-Control-Allow-Credentials', 'true');
    }

}

Then when defining the Micro app:

$di->set('cors', function() {
    return new CORSPlugin;
}, true);

$em = new EventsManager;
$em->attach('micro:beforeHandleRoute', $di->get('cors'));

$app = new Micro($di);
$app->setEventsManager($em);
edited Feb '17

Edited: It was the Access-Control-Allow-Origin that was wrong. Sorry

Hi all,

I using the @Pierre solution. It works well, but after the 200 OK response to the options my app does not manage the POST. Any clue?

I have added a bit more to the class in-case anyone else wants cors and preflight in their apps.

I am not using micro on this project so this on an application.

I created a listener class that extends Phalcon\Di\Injectable

<?php

namespace RealWorld\Listener;

use Phalcon\Events\Event;
use Phalcon\Di\Injectable;
use Phalcon\Http\Request;
use Phalcon\Http\Response;
use Phalcon\Mvc\Dispatcher;

/**
 * Class PreFlightListener
 * @package RealWorld\Listener
 * @property Request $request
 * @property Response $response
 */
class PreFlightListener extends Injectable
{
    /**
     * @param Event $event
     * @param Dispatcher $dispatcher
     */
    public function beforeExecuteRoute(Event $event, Dispatcher $dispatcher) {
        $di = $dispatcher->getDI();
        $request = $di->get('request');
        $response = $di->get('response');

        if ($this->isCorsRequest($request)) {
            $response
                ->setHeader('Access-Control-Allow-Origin', $this->getOrigin($request))
                ->setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE')
                ->setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Range, Content-Disposition, Content-Type, Authorization')
                ->setHeader('Access-Control-Allow-Credentials', 'true');
        }

        if ($this->isPreflightRequest($request)) {
            $response->setStatusCode(200, 'OK')->send(); exit;
        }
    }

    /**
     * @param Request $request
     * @return bool
     */
    public function isCorsRequest(Request $request)
    {
        return !empty($request->getHeader('Origin')) && !$this->isSameHost($request);
    }

    /**
     * @param Request $request
     * @return bool
     */
    public function isPreflightRequest(Request $request)
    {
        return $this->isCorsRequest($request)
            && $request->getMethod() === 'OPTIONS'
            && !empty($request->getHeader('Access-Control-Request-Method'));
    }

    /**
     * @param Request $request
     * @return bool
     */
    public function isSameHost(Request $request)
    {
        return $request->getHeader('Origin') === $this->getSchemeAndHttpHost($request);
    }

    /**
     * @param Request $request
     * @return string
     */
    public function getSchemeAndHttpHost(Request $request)
    {
        return $request->getScheme() . '://' . $request->getHttpHost();
    }

    /**
     * @param Request $request
     * @return string
     */
    public function getOrigin(Request $request)
    {
        return $request->getHeader('Origin') ? $request->getHeader('Origin') : '*';
    }
}

Then in services.php I registered the preflight service:

$di->set('preflight', function() {
    return new PreFlightListener();
}, true);

I then attached the class to an event in the disptacher service:

$di->setShared(
    "dispatcher",
    function () use ($di) {
        $eventsManager = new Manager();

        // Attach a listener
        $eventsManager->attach("dispatch:beforeExecuteRoute", $di->get('preflight'));

        $dispatcher = new Dispatcher();
        $dispatcher->setEventsManager($eventsManager);
        $dispatcher->setDefaultNamespace("RealWorld\\Controllers");

        return $dispatcher;
    }
);

I don't like the exit in the preflight check but not sure how to send back the headers without an error occurring.