Written by James McDonald

July 6, 2019

Before fetch sends a POST request to another domain it will do a CORS pre-flight check. The following website has the HTTP OPTIONS middleware code you can use for CakePHP 3. It mentions for a CakePHP 2 version you need to go to another website which is unavailable now (July 2019). See below for my CakePHP 2 code do do the same thing.

CakePHP 2.x DispatchFilter CORS ‘OPTIONS’ pre-flight check response code

This basically says if the request is an OPTIONS check is from the right origin and the requested method is allowed respond with the right headers.

<?php

/**
 * CakePHP 2 DispatchFilter code for CORS OPTIONS pre-flight check
 * fetch sends an OPTIONS pre-flight check
 * copy this file as OptionsFilter.php to app/Routing/Filter/
 * load this DispatcherFilter by finding this block of code in
 * app/Config/bootstrap.php and appending the
 * 'OptionsFilter' entry
 * Configure::write('Dispatcher.filters', [
 *    'AssetDispatcher',
 *    'CacheDispatcher',
 *    'OptionsFilter' // custom by tgn
 * ]);
 *
 */

App::uses('DispatcherFilter', 'Routing');
App::uses('Configure', 'Core');

class OptionsFilter extends DispatcherFilter
{

    /**
     * @var int
     */
    public $priority = 9;

    /**
     * @param CakeEvent $event
     * @return mixed
     */
    public function beforeDispatch(CakeEvent $event)
    {
        $request = $event->data['request'];
        $response = $event->data['response'];

        // here is your allowed origins
        // I set them using Configure in bootstrap.php
        // Configure::write('ALLOWED_METHODS', ['PUT', 'POST', 'DELETE']);
        // Configure::write('ALLOWED_ORIGINS',['http://localhost:3000', 'http://localhost:8081']);

        $allowedOrigins = Configure::read('ALLOWED_ORIGINS');
        // here are the allowed methods
        $allowedMethods = Configure::read('ALLOWED_METHODS');

        // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age
        // 2 hours
        $accessControlMaxAge = '7200';

        if ($request->is('OPTIONS')) {

            // where is the request coming from
            // e.g. from my react dev environment
            // it is http://localhost:3000
            $origin = $request->header('Origin');

            // what method are they requesting to use
            $method = $request->header('Access-Control-Request-Method');

            // what headers are they sending
            $headers = $request->header('Access-Control-Request-Headers');

            // if allow then set headers
            if (in_array($origin, $allowedOrigins)) {
                $response->header(
                    'Access-Control-Allow-Origin',
                    $origin
                );
            }

            if (in_array($method, $allowedMethods)) {
                $response->header(
                    "Access-Control-Allow-Methods", $method
                );
            }

            $response->header(
                "Access-Control-Allow-Headers",
                $headers
            );
            $response->header('Access-Control-Allow-Credentials', "true");
            $response->header('Access-Control-Max-Age', $accessControlMaxAge);
            $response->header('Content-Type', 'application/json');

            // this is just me goofing off
            // but this response viewed in the dev tools of the
            // browsers lets me know
            // I have triggered the DispatchFilter
            $response->body(
                json_encode(
                    [
                        'options' => 'pre-flight from dispatcher'
                    ]
                )
            );
            $event->stopPropagation();
            return $response;
        }

    }
}

Code to put in CakePHP 2.x Actions to allow POST

  public function add($shipment_type = null)
    {

        $last = null;
        $error = null;

        $origin = $this->request->header('Origin');
        $allowedOrigins = Configure::read('ALLOWED_ORIGINS');
        if (in_array($origin, $allowedOrigins)) {
            $this->response->header('Access-Control-Allow-Origin', $origin);
        }
        // rest of your action code

Chrome DevTools View of the OPTIONS pre-flight

Response from the CakePHP 2.x DispatcherFilter code above

Note the OPTIONS Request Headers are Access-Control-Request-Headers: content-type, x-requested-with, Access-Control-Request-Method: POST and Origin: http://localhost:3000

The dispatch filter sends the following headers as response

  1. Access-Control-Allow-Credentials: true
  2. Access-Control-Allow-Headers: content-type,x-requested-with
  3. Access-Control-Allow-Methods: POST
  4. Access-Control-Allow-Origin: http://localhost:3000
  5. Access-Control-Max-Age: 7200

You may have seen ‘Access-Control-Allow-Origin’ set to ‘*’

POST request after receiving the correct response headers

This is the fetch code in a react App that generates the OPTIONS and POST request.

fetch(url, {
	method: "POST", // *GET, POST, PUT, DELETE, etc.
	mode: "cors", // no-cors, cors, *same-origin
	cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached
	credentials: "same-origin", // include, *same-origin, omit
	headers: {
		"Content-Type": "application/json",
		Accept: "application/json",
		"X-Requested-With": "XMLHttpRequest"
	},
	redirect: "error", // manual, *follow, error
	//referrer: "no-referrer", // no-referrer, *client
	body: JSON.stringify(postObject) // body data type must match "Content-Type" header
}).then(response => response.json());

CakePHP 3 Options Middleware Code

Just in case the original website goes away

<?php

namespace App\Middleware;

class HttpOptionsMiddleware
{
    public function __invoke($request, $response, $next)
    {
        $response = $response->withHeader('Access-Control-Allow-Origin', '*');

        if ($request->getMethod() == 'OPTIONS')
        {
            $method = $request->getHeader('Access-Control-Request-Method');
            $headers = $request->getHeader('Access-Control-Request-Headers');
            $allowed = empty($method) ? 'GET, POST, PUT, DELETE' : $method;

            $response = $response
                            ->withHeader('Access-Control-Allow-Headers', $headers)
                            ->withHeader('Access-Control-Allow-Methods', $allowed)
                            ->withHeader('Access-Control-Allow-Credentials', 'true')
                            ->withHeader('Access-Control-Max-Age', '86400');

            return $response;
        }

        return $next($request, $response);
    }
}

Register Middleware in src/Application for CakePHP 3.x

To install the CakePHP 3 httpOptionsMiddleware

mkdir src/Middleware

create a new file named src/Middleware/HttpOptionsMiddleware.php and put the contents of the below code into it

Open src/Application.php include a use statement for the code and append the new hook code for the middleware:

<?php
use App\Middleware\HttpOptionsMiddleware;

class Application extends BaseApplication {
 public function middleware($middlewareQueue)
    {
        $middlewareQueue
        // Add routing middleware.
        ->add(new RoutingMiddleware($this))
        //... other middleware etc
        // add http options
        ->add( new HttpOptionsMiddleware($this));
        return $middlewareQueue;
     }

0 Comments

Submit a Comment

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

The reCAPTCHA verification period has expired. Please reload the page.

You May Also Like…

Squarespace Image Export

To gain continued access to your Squarespace website images after cancelling your subscription you have several...

MySQL 8.x GRANT ALL STATEMENT

-- CREATE CREATE USER 'tgnrestoreuser'@'localhost' IDENTIFIED BY 'AppleSauceLoveBird2024'; GRANT ALL PRIVILEGES ON...

Exetel Opt-Out of CGNAT

If your port forwards and inbound and/or outbound site-to-site VPN's have failed when switching to Exetel due to their...