caitlin
caitlin

Reputation: 2819

CakePHP2: CORS Preflight issues with JWT Auth

I'm building an Ionic 1 app that talks to a REST API implemented in CakePHP 2.8, using JSON Web Tokens (JWT) for auth.

In an unauthorized state, my app is able to make GET / POST requests to the server with no issue. However, once I have authenticated and my app is sending a authToken header along with every request, Angular automatically sends an OPTIONS preflight request first.

This is where the problem starts. Because the automatic preflight request does not have the authToken header set, and because the API endpoint requires authorization, CakePHP responds with a 302 FOUND redirect to /login. The app (or browser, at this testing phase) considers this unsafe, and never goes on to make the proper request.

My question: How can I get CakePHP to respond appropriately to preflight OPTIONS requests so that AngularJS knows it can safely send a custom header to a cross-domain server?

Per this question, this is what needs to happen:

Request headers:

Origin: http://yourdomain.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-Custom-Header

Response headers:

Access-Control-Allow-Origin: http://yourdomain.com // Same as origin above
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: X-Custom-Header

Things I've tried:

-Setting .htaccess to always respond to OPTIONS requests with a 200. I get a 200 response, but the response says "your server is misconfigured" and, because it doesn't have the proper headers set, the real $http request never goes through.

-Getting CakePHP to always respond a certain way to OPTIONS requests. The issue here is getting Cake to skip authorization, skip trying to run the controller action, and send back a HTTP 200 response with the proper headers set.

    // AppController::beforeFilter();
    if($this->request->is("options")){
        // Send the appropriate response based on the request
    }

For the curious, here are the headers that get exchanged in the course of a failed $http request:

GENERAL:
Request URL:https://api.example.com/rest_users/profile.json
Request Method:OPTIONS
Status Code:302 Found
Remote Address:x.x.x.x:443

REQUEST HEADERS:
Accept:*/*
Accept-Encoding:gzip, deflate, sdch, br
Accept-Language:en-US
Access-Control-Request-Headers:authtoken
Access-Control-Request-Method:GET
Cache-Control:no-cache
Connection:keep-alive
DNT:1
Host:api.example.com
Origin:http://x.x.x.x:8100
Pragma:no-cache
Referer:http://x.x.x.x:8100/
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36


RESPONSE HEADERS:
Access-Control-Allow-Headers:Content-Type, x-xsrf-token
Access-Control-Allow-Methods:*
Access-Control-Allow-Origin:*
Access-Control-Max-Age:172800
Connection:Keep-Alive
Content-Encoding:gzip
Content-Length:20
Content-Type:text/html; charset=UTF-8
Date:Thu, 29 Dec 2016 18:51:30 GMT
Keep-Alive:timeout=15, max=99
Location:https://api.example.com/login
Server:Apache/2.2.15 (CentOS)
Vary:Accept-Encoding
X-Powered-By:PHP/5.3.3

Upvotes: 4

Views: 1937

Answers (1)

caitlin
caitlin

Reputation: 2819

I found a solution that allows CakePHP to handle CORS preflights correctly. It sets the headers, sends the response, and shuts down Cake before the requested action can run.

Be sure to have parent::beforeFilter(); in all your controllers so that this code runs.

In AppController::beforeFilter():

    if($this->request->is("options")){
        // Set the headers
        $this->response->header('Access-Control-Allow-Origin','*');
        $this->response->header('Access-Control-Allow-Methods','*');
        $this->response->header('Access-Control-Allow-Headers','Content-Type, Authorization');
        // Send the response
        $this->response->send();
        // Don't do anything else!
        $this->_stop();
    }

Upvotes: 2

Related Questions