OPTIONS pre-flight Check CakePHP 2

by | Jul 6, 2019 | IT Tips, Wordpress | 0 comments

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
<?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');
 
        // 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

1
2
3
4
5
6
7
8
9
10
11
12
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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.