Subscribe to PHP Freaks RSS

Implementing CORS in Zend Expressive

syndicated from planet-php.net on November 15, 2017

On a recent project, I needed to implement CORS support for my Expressive API. The easiest way to do this is to use Mike Tuupola's PSR-7 CORS Middleware.

As this is a standard Slim-Style PSR-7 middleware implementation, we need to wrap it for Expressive, so we make a factory:

App/Factory/CorsMiddlewareFactory.php:

<?php declare(strict_types=1);
namespace App\Factory;

use Tuupola\Middleware\Cors; use Zend\Diactoros\Response; use Zend\Stratigility\Middleware\CallableMiddlewareWrapper;

class CorsMiddlewareFactory { public function __invoke($container) { return new CallableMiddlewareWrapper( new Cors([ "origin" => ["*"], "methods" => ["GET", "POST", "PUT", "PATCH", "DELETE"], "headers.allow" => ["Content-Type", "Accept"], "headers.expose" => [], "credentials" => false, "cache" => 0, ]), new Response() ); } }

We then register this in our App\ConfigProvider::getDependencies() by adding to the factories key so that it looks something like this:

'factories'  => [
        Action\HomePageAction::class => Action\HomePageFactory::class,
        \Tuupola\Middleware\Cors::class => Factory\CorsMiddlewareFactory::class,
    ],

If you don't want to fully qualify, add use Tuupola\Middleware\Cors; to the top of the file so that you can just use Cors::class here.

Lastly, we register the middleware in config/pipeline.php:

$app->pipe(\Tuupola\Middleware\Cors::class);

Place it somewhere near the top of the list; personally, I place it just after piping the ServerUrlMiddleware::class.

We now have working CORS:

$ curl -X OPTIONS -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: Accept, Content-Type"  \
  -H "Origin: http://localhost" -H "Accept: application/json" http://localhost:8890/
HTTP/1.1 200 OK
Host: localhost:8890
Connection: close
Access-Control-Allow-Origin: http://localhost
Vary: Origin
Access-Control-Allow-Headers: content-type, accept
Content-type: text/html; charset=UTF-8

Failure looks like this:

$ curl -X OPTIONS -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: Accept, Content-Type X-Clacks-Overhead"  \
  -H "Origin: http://localhost" -H "Accept: application/json" http://localhost:8890/
HTTP/1.1 401 Unauthorized
Host: localhost:8890
Connection: close
Content-type: text/html; charset=UTF-8

By default, it doesn't tell you what went wrong, which isn't too helpful.

Providing JSON error responses

To provide a JSON error response, you need to set the error option to a callable and you can then return a JsonResponse:

App/Factory/CorsMiddleware.php:

<?php declare(strict_types=1);
namespace App\Factory;

use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Tuupola\Middleware\Cors; use Zend\Diactoros\Response; use Zend\Diactoros\Response\JsonResponse; use Zend\Stratigility\Middleware\CallableMiddlewareWrapper;

class CorsMiddlewareFactory { public function __invoke($container) { return new CallableMiddlewareWrapper( new Cors([ "origin" => ["*"], "methods" => ["GET", "POST", "PUT", "PATCH", "DELETE"], "headers.allow" => ["Content-Type", "Accept"], "headers.expose" => [], "credentials" => false, "cache" => 0, "error" => [$this, 'error'], ]), new Response() ); }

public static function error( RequestInterface $request, ResponseInterface $response, $arguments) {

return new JsonResponse($arguments); } }

As you can see, we've created a new error method that returns a JsonResponse. We can then encode the $arguments as that contains the information about what the problem is:

$ curl -X OPTIONS -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: Accept, Content-Type X-Clacks-Overhead"  \
  -H "Origin: http://localhost" -H "Accept: application/json" http://localhost:8890/
HTTP/1.1 401 Unauthorized
Host: localhost:8890
Date: Sat, 21 Oct 2017 10:41:18 +0000
Connection: close
X-Powered-By: PHP/7.1.8
Content-Type: application/json

{"message":"CORS requested h

Truncated by Planet PHP, read more at the original (another 1594 bytes)