Giter Club home page Giter Club logo

cors-middleware's Introduction

PSR-7 and PSR-15 CORS Middleware

This middleware implements Cross-origin resource sharing. It supports both PSR-7 style doublepass and PSR-15 middleware standards. It has been tested with Slim Framework and Zend Expressive. Internally the middleware uses neomerx/cors-psr7 library for heavy lifting.

Latest Version Packagist Software License Build Status Coverage

Install

Install using composer.

$ composer require tuupola/cors-middleware

Usage

Documentation assumes you have working knowledge of CORS. There are no mandatory parameters. If you are using Zend Expressive skeleton middlewares are added to file called config/pipeline.php. Note that you must disable the default ImplicitOptionsMiddleware for this middleware to work.

use Tuupola\Middleware\CorsMiddleware;

#$app->pipe(ImplicitOptionsMiddleware::class);
$app->pipe(CorsMiddleware::class);

Slim Framework does not have specified config files. Otherwise adding the middleware is similar with previous.

$app->add(new Tuupola\Middleware\CorsMiddleware);

Rest of the examples use Slim Framework.

If called without any parameters the following defaults are used.

$app->add(new Tuupola\Middleware\CorsMiddleware([
    "origin" => ["*"],
    "methods" => ["GET", "POST", "PUT", "PATCH", "DELETE"],
    "headers.allow" => [],
    "headers.expose" => [],
    "credentials" => false,
    "cache" => 0,
]));
$ curl "https://api.example.com/" \
    --request OPTIONS \
    --include
    --header "Access-Control-Request-Method: PUT" \
    --header "Origin: http://www.example.com"

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://www.example.com
Vary: Origin
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE

However, you most likely want to change some of the defaults. For example if developing a REST API which supports caching and conditional requests you could use the following.

$app->add(new Tuupola\Middleware\CorsMiddleware([
    "origin" => ["*"],
    "methods" => ["GET", "POST", "PUT", "PATCH", "DELETE"],
    "headers.allow" => ["Authorization", "If-Match", "If-Unmodified-Since"],
    "headers.expose" => ["Etag"],
    "credentials" => true,
    "cache" => 86400
]));
$ curl "https://api.example.com/foo" \
    --request OPTIONS \
    --include \
    --header "Origin: http://www.example.com" \
    --header "Access-Control-Request-Method: PUT" \
    --header "Access-Control-Request-Headers: Authorization, If-Match"

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://www.example.com
Access-Control-Allow-Credentials: true
Vary: Origin
Access-Control-Max-Age: 86400
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE
Access-Control-Allow-Headers: authorization, if-match, if-unmodified-since
$ curl "https://api.example.com/foo" \
    --request PUT \
    --include \
    --header "Origin: http://www.example.com"

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://www.example.com
Access-Control-Allow-Credentials: true
Vary: Origin
Access-Control-Expose-Headers: Etag

Parameters

Origin

By default all origins are allowed. You can limit allowed origins by passing them as an array.

$app->add(new Tuupola\Middleware\CorsMiddleware([
    "origin" => ["app-1.example.com", "app-2.example.com"]
]));

You can also use wildcards to define multiple origins at once. Wildcards are matched by using the fnmatch() function.

$app->add(new Tuupola\Middleware\CorsMiddleware([
    "origin" => ["*.example.com"]
]));

Methods

Methods can be passed either as an array or a callable which returns an array. Below example is for Zend Expressive where value of methods is dynamic depending on the requested route.

use Tuupola\Middleware\CorsMiddleware;
use Zend\Expressive\Router\RouteResult;

$app->pipe(new CorsMiddleware([
    "origin" => ["*"],
    "methods" => function($request) {
        $result = $request->getAttribute(RouteResult::class);
        $route = $result->getMatchedRoute();
        return $route->getAllowedMethods();
    }
]));

Same thing for Slim 3. This assumes you have not defined the OPTIONS route.

use Fastroute\Dispatcher;
use Tuupola\Middleware\CorsMiddleware;

$app->add(
    new CorsMiddleware([
        "origin" => ["*"],
        "methods" => function($request) use ($app) {
            $container = $app->getContainer();
            $dispatch = $container["router"]->dispatch($request);
            if (Dispatcher::METHOD_NOT_ALLOWED === $dispatch[0]) {
                return $dispatch[1];
            }
        }
    ])
);

Logger

The optional logger parameter allows you to pass in a PSR-3 compatible logger to help with debugging or other application logging needs.

$logger = Monolog\Logger("slim");
$rotating = new RotatingFileHandler(__DIR__ . "/logs/slim.log", 0, Logger::DEBUG);
$logger->pushHandler($rotating);

$app->add(new Tuupola\Middleware\CorsMiddleware([
    "logger" => $logger,
]));

Error

Error is called when CORS request fails. It receives last error message in arguments. This can be used for example to create application/json responses when CORS request fails.

$app->add(new Tuupola\Middleware\CorsMiddleware([
    "methods" => ["GET", "POST", "PUT"],
    "error" => function ($request, $response, $arguments) {
        $data["status"] = "error";
        $data["message"] = $arguments["message"];
        return $response
            ->withHeader("Content-Type", "application/json")
            ->write(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
    }
]));
$ curl https://api.example.com/foo \
    --request OPTIONS \
    --include \
    --header "Access-Control-Request-Method: PATCH" \
    --header "Origin: http://www.example.com"

HTTP/1.1 401 Unauthorized
Content-Type: application/json
Content-Length: 83

{
    "status": "error",
    "message": "CORS requested method is not supported."
}

Server origin

If your same-origin requests contain an unnecessary Origin header, they might get blocked in case the server origin is not among the allowed origins already. In this case you can use the optional origin.server parameter to specify the origin of the server.

$app->add(new Tuupola\Middleware\CorsMiddleware([
    "origin.server" => "https://example.com"
]));
$ curl https://example.com/api \
    --request POST \
    --include \
    --header "Origin: https://example.com"

HTTP/1.1 200 OK

Testing

You can run tests either manually or automatically on every code change. Automatic tests require entr to work.

$ make test
$ brew install entr
$ make watch

Contributing

Please see CONTRIBUTING for details.

Security

If you discover any security related issues, please email [email protected] instead of using the issue tracker.

License

The MIT License (MIT). Please see License File for more information.

cors-middleware's People

Contributors

fabacino avatar jankonas avatar mikhailidi avatar pnoexz avatar tuupola avatar usox avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

cors-middleware's Issues

Same-Origin requests are blocked when Origin header is present

If a same-origin request is sent with an (unnecessary) origin header, the middleware blocks the request unless the server origin is explicitly added to the allowed origins. This is afaik not technically wrong, but still needs to be handled manually when using the middleware, since at least Chrome sends these unnecessary origin headers all the time.

$app->add(new \Tuupola\Middleware\CorsMiddleware([
    'origin' => []
]));
curl --request POST 'https://www.example.com'
     --include
     --header 'Origin: https://www.example.com'
HTTP/1.1 401 Unauthorized
Date: Tue, 14 Aug 2018 14:36:45 GMT
Server: Apache/2.4.34 (Unix) OpenSSL/1.1.0h PHP/7.2.8
X-Powered-By: PHP/7.2.8
Content-Length: 0

I think it would be nice if the middleware provided an additional option to pass a value for the server origin to the Strategies\Settings class of the underlying neomerx/cors-psr7 library (there already exists a function setServerOrigin in that class).

I can PR if you think this might be useful.

How to use with php-di?

Question,

How to use this with PHP-DI for example?

return [ 'middlewares' => [ autowire(ErrorHandlingMiddleware::class), autowire(ClaimMiddleware::class) ]

I tried but i'm not able to set the options like this way

OPTIONS passes, GET fails - CORS Missing Allow Origin

Hello, I am using Slim3 and php7.2, and as the subject says, OPTIONS passes with a 200 and I get the Missing Allow Origin with the GET request and a 500 response. Everything works fine on postman so I know the code is ok.

here is my config for the middleware:

$app->add(new Tuupola\Middleware\CorsMiddleware([
    "origin" => ["https://admin.fiftyflowers.com"],
    "methods" => ["GET", "POST", "PUT", "PATCH", "DELETE"],
    "headers.allow" => ["Authorization", "Access-Control-Allow-Headers", "Range", "Cache-Control", "If-Modified-Since",  "If-Match", "If-Unmodified-Since", "Content-Type"],
    "headers.expose" => ["Etag"],
    "credentials" => true,
    "cache" => 0
]));

Order of operations

Hi! An Expressive user was using your middleware recently, and couldn't figure out why the CORS headers were not being set. In the end, they realized that they were creating a response within a lower layer and returning it:

return new JsonResponse($data);

The problem is that cors-middleware injects the response that it passes to $next() - which means the headers are then lost if an inner layer creates and returns a new response instance.

I suggest that the following lines:

  • $cors_headers = $cors->getResponseHeaders();
    foreach ($cors_headers as $header => $value) {
    /* Diactoros errors on integer values. */
    if (false === is_array($value)) {
    $value = (string)$value;
    }
    $response = $response->withHeader($header, $value);
    }

be updated to operate on the returned response instead. This will guarantee they are injected.

AssertionError in neomerx/cors-psr7/src/Strategies/Settings.php line 267 assert(empty($host) === false)

I'm getting an AssertionError since 1.4 on running integration tests:

POST http://localhost:8080/oauth2/token

 AssertionError raised in file /app/vendor/neomerx/cors-psr7/src/Strategies/Settings.php line 267:
  Message: assert(empty($host) === false)
  Stack Trace:
  #0 /app/vendor/neomerx/cors-psr7/src/Strategies/Settings.php(267): assert(false, 'assert(empty($h...')
  #1 /app/vendor/neomerx/cors-psr7/src/Strategies/Settings.php(154): Neomerx\Cors\Strategies\Settings->setServerOrigin('https', '', 443)
  #2 /app/vendor/tuupola/cors-middleware/src/CorsMiddleware.php(230): Neomerx\Cors\Strategies\Settings->init('https', '', 443)
  #3 /app/vendor/tuupola/cors-middleware/src/CorsMiddleware.php(133): Tuupola\Middleware\CorsMiddleware->buildSettings(Object(Laminas\Diactoros\ServerRequest), Object(Laminas\Diactoros\Response))
  #4 /app/vendor/tuupola/callable-handler/src/DoublePassTrait.php(47): Tuupola\Middleware\CorsMiddleware->process(Object(Laminas\Diactoros\ServerRequest), Object(Tuupola\Middleware\CallableHandler))
  #5 /app/vendor/laminas/laminas-stratigility/src/Middleware/DoublePassMiddlewareDecorator.php(67): Tuupola\Middleware\CorsMiddleware->__invoke(Object(Laminas\Diactoros\ServerRequest), Object(Laminas\Diactoros\Response), Object(Closure))
  #6 /app/vendor/mezzio/mezzio/src/Middleware/LazyLoadingMiddleware.php(37): Laminas\Stratigility\Middleware\DoublePassMiddlewareDecorator->process(Object(Laminas\Diactoros\ServerRequest), Object(Laminas\Stratigility\Next))
  #7 /app/vendor/laminas/laminas-stratigility/src/Next.php(49): Mezzio\Middleware\LazyLoadingMiddleware->process(Object(Laminas\Diactoros\ServerRequest), Object(Laminas\Stratigility\Next))
  #8 /app/vendor/mezzio/mezzio-router/src/Middleware/RouteMiddleware.php(50): Laminas\Stratigility\Next->handle(Object(Laminas\Diactoros\ServerRequest))
  #9 /app/vendor/mezzio/mezzio/src/Middleware/LazyLoadingMiddleware.php(37): Mezzio\Router\Middleware\RouteMiddleware->process(Object(Laminas\Diactoros\ServerRequest), Object(Laminas\Stratigility\Next))
  #10 /app/vendor/laminas/laminas-stratigility/src/Next.php(49): Mezzio\Middleware\LazyLoadingMiddleware->process(Object(Laminas\Diactoros\ServerRequest), Object(Laminas\Stratigility\Next))
  #11 /app/vendor/mezzio/mezzio-helpers/src/ServerUrlMiddleware.php(30): Laminas\Stratigility\Next->handle(Object(Laminas\Diactoros\ServerRequest))
  #12 /app/vendor/mezzio/mezzio/src/Middleware/LazyLoadingMiddleware.php(37): Mezzio\Helper\ServerUrlMiddleware->process(Object(Laminas\Diactoros\ServerRequest), Object(Laminas\Stratigility\Next))
  #13 /app/vendor/laminas/laminas-stratigility/src/Next.php(49): Mezzio\Middleware\LazyLoadingMiddleware->process(Object(Laminas\Diactoros\ServerRequest), Object(Laminas\Stratigility\Next))
  #14 /app/vendor/laminas/laminas-stratigility/src/Middleware/ErrorHandler.php(129): Laminas\Stratigility\Next->handle(Object(Laminas\Diactoros\ServerRequest))
  #15 /app/vendor/mezzio/mezzio/src/Middleware/LazyLoadingMiddleware.php(37): Laminas\Stratigility\Middleware\ErrorHandler->process(Object(Laminas\Diactoros\ServerRequest), Object(Laminas\Stratigility\Next))
  #16 /app/vendor/laminas/laminas-stratigility/src/Next.php(49): Mezzio\Middleware\LazyLoadingMiddleware->process(Object(Laminas\Diactoros\ServerRequest), Object(Laminas\Stratigility\Next))
  #17 /app/vendor/laminas/laminas-stratigility/src/MiddlewarePipe.php(75): Laminas\Stratigility\Next->handle(Object(Laminas\Diactoros\ServerRequest))
  #18 /app/vendor/laminas/laminas-stratigility/src/MiddlewarePipe.php(64): Laminas\Stratigility\MiddlewarePipe->process(Object(Laminas\Diactoros\ServerRequest), Object(Laminas\Stratigility\EmptyPipelineHandler))
  #19 /app/vendor/mezzio/mezzio-swoole/src/Event/RequestHandlerRequestListener.php(116): Laminas\Stratigility\MiddlewarePipe->handle(Object(Laminas\Diactoros\ServerRequest))
  #20 /app/vendor/mezzio/mezzio-swoole/src/Event/EventDispatcher.php(39): Mezzio\Swoole\Event\RequestHandlerRequestListener->__invoke(Object(Mezzio\Swoole\Event\RequestEvent))
  #21 /app/vendor/mezzio/mezzio-swoole/src/SwooleRequestHandlerRunner.php(135): Mezzio\Swoole\Event\EventDispatcher->dispatch(Object(Mezzio\Swoole\Event\RequestEvent))
  #22 [internal function]: Mezzio\Swoole\SwooleRequestHandlerRunner->onRequest(Object(Swoole\Http\Request), Object(Swoole\Http\Response))
  #23 /app/vendor/mezzio/mezzio-swoole/src/SwooleRequestHandlerRunner.php(79): Swoole\Server->start()
  #24 /app/vendor/mezzio/mezzio/src/Application.php(68): Mezzio\Swoole\SwooleRequestHandlerRunner->run()
  #25 /app/bin/server.php(28): Mezzio\Application->run()
  #26 /app/bin/server.php(29): {closure}()
  #27 {main}

It's a laminas/mezzio API using Swoole with the following config:

return [
    'cors' => [
        'options' => [
            'origin'        => '*',
            'headers.allow' => [
                // "simple headers" are always allowed
                'Accept',
                'Accept-Language',
                'Content-Language',

                // Content-Type is a "simple header",
                // but only with a MIME type of its parsed value (ignoring parameters)
                // of either application/x-www-form-urlencoded, multipart/form-data, or text/plain
                'Content-Type',

                // allow additional headers
                'Authorization',
            ],
            'headers.expose' => ['Content-Length', 'Date'],
            'cache'          => 60 * 5,
        ],
    ],
];

assert(false === empty($host)) AssertionError

Details
Type: AssertionError
Code: 1
Message: assert(false === empty($host))
File: /var/www/vendor/neomerx/cors-psr7/src/Strategies/Settings.php
Line: 188
Trace
#0 /var/www/vendor/neomerx/cors-psr7/src/Strategies/Settings.php(188): assert(false, 'assert(false ==...')
#1 /var/www/vendor/neomerx/cors-psr7/src/Strategies/Settings.php(90): Neomerx\Cors\Strategies\Settings->setServerOrigin('https', '', 443)
#2 /var/www/vendor/tuupola/cors-middleware/src/CorsMiddleware.php(230): Neomerx\Cors\Strategies\Settings->init('https', '', 443)
#3 /var/www/vendor/tuupola/cors-middleware/src/CorsMiddleware.php(133): Tuupola\Middleware\CorsMiddleware->buildSettings(Object(GuzzleHttp\Psr7\ServerRequest), Object(GuzzleHttp\Psr7\Response))
#4 /var/www/vendor/slim/slim/Slim/MiddlewareDispatcher.php(147): Tuupola\Middleware\CorsMiddleware->process(Object(GuzzleHttp\Psr7\ServerRequest), Object(Slim\Routing\RouteRunner))
#5 /var/www/vendor/slim/slim/Slim/Middleware/ErrorMiddleware.php(107): Psr\Http\Server\RequestHandlerInterface@anonymous->handle(Object(GuzzleHttp\Psr7\ServerRequest))
#6 /var/www/vendor/slim/slim/Slim/MiddlewareDispatcher.php(147): Slim\Middleware\ErrorMiddleware->process(Object(GuzzleHttp\Psr7\ServerRequest), Object(Psr\Http\Server\RequestHandlerInterface@anonymous))
#7 /var/www/vendor/slim/slim/Slim/MiddlewareDispatcher.php(81): Psr\Http\Server\RequestHandlerInterface@anonymous->handle(Object(GuzzleHttp\Psr7\ServerRequest))
#8 /var/www/vendor/slim/slim/Slim/App.php(215): Slim\MiddlewareDispatcher->handle(Object(GuzzleHttp\Psr7\ServerRequest))
#9 /var/www/vendor/slim/slim/Slim/App.php(199): Slim\App->handle(Object(GuzzleHttp\Psr7\ServerRequest))
#10 /var/www/public/index.php(88): Slim\App->run()

my code

$app = AppFactory::create();
$app->add(new CorsMiddleware());
...
$app->run();

CorsMiddleware->process() creates a response even tough its passed a response object

Hi! I'm really enjoying your project, thank you very much.

I have a question about, why in CorsMiddleware->process(ServerRequestInterface $request, RequestHandlerInterface $handler) there's an instantiation of a response object?

That's because in the $handler that is instantiated in DoublePassTrait->__invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next) already has a response as a property.

This instantiated response inside CorsMiddleware->process(...) is used in all cases except in the actual CORS request, why in this case is used the request property from the handler?

I think I'm missing something, it would be very enlightening if you could response these questions.

Uncaught BadMethodCallException

I recently installed the middleware and added the lines acording to the documentation, but it fails with the following error:

PHP Fatal error: Uncaught BadMethodCallException: Method pipe is not a valid method in /var/www/vendor/slim/slim/Slim/App.php:125\nStack trace:\n#0 /var/www/public/index.php(16): Slim\App->__call('pipe', Array)\n#1 {main}\n thrown in /var/www/vendor/slim/slim/Slim/App.php on line 125

mi index.php looks like:

require __DIR__ . '/../vendor/autoload.php';
use Tuupola\Middleware\CorsMiddleware;
// Instantiate the app
$settings = require __DIR__ . '/../src/settings.php';
$app = new \Slim\App($settings);
$database = require __DIR__ . '/../src/settingsDB.php';
// Set up dependencies
$dependencies = require __DIR__ . '/../src/dependencies.php';
$dependencies($app);
$app->pipe(CorsMiddleware::class);
$app->add(new Tuupola\Middleware\CorsMiddleware);
// Register middleware
$middleware = require __DIR__ . '/../src/middleware.php';
$middleware($app);
// Register routes
$routes = require __DIR__ . '/../src/routes.php';
$routes($app);

Thanks and sorry if it's a newbie and easy question.

Unable to get this working in Slim

I wanted to make sure: is the documentation up to date? I've tried this in various formats, and it doesn't work, and looking at the code, I don't really understand how what's mentioned in the documentation would work, but I'm chalking that up to my not understanding PSR 7 and 15.

I'm on Slim 3.9.2. So far I've tried:

$app->add(new Tuupola\Middleware\CorsMiddleware);

$app->add(new Tuupola\Middleware\CorsMiddleware([
	"origin" => [env('FRONTEND_DOMAIN')],
	"methods" => ['*'],
	"headers.allow" => ['*'],
	"headers.expose" => [],
	"credentials" => false,
	"cache" => 0,
]);

$app->add(function (Request $request, Response $response, $next) {
	$corsMiddleware = new Tuupola\Middleware\CorsMiddleware([
		"origin" => [env('FRONTEND_DOMAIN')],
		"methods" => $methods,
		"headers.allow" => $request->getHeader('access-control-request-headers'),
		"headers.expose" => [],
		"credentials" => false,
		"cache" => 0,
	]);
	return $corsMiddleware;;
});

I've also tried the last one returning ->process($request, $response).

Nothing seems to add any CORS headers to my calls. I'd love to find out where I'm going wrong.

Failed to load http://localhost:8080/api/login: Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.

Chrome is giving me this error when trying to make a call. The request is returning 200, but Chrome won't release the request to my javascript.

I'm just trying to get a minimal example working:

$app->pipe(new CorsMiddleware([
        "origin" => ["*"],
        "methods" => ["GET", "POST", "PUT", "PATCH", "DELETE"],
        "headers.allow" => ["*"],
        "headers.expose" => ["Etag"],
        "credentials" => true,
        "cache" => 86400
    ]));
OPTIONS /api/login HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Access-Control-Request-Method: POST
Origin: http://localhost:4200
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36
Access-Control-Request-Headers: content-type
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en;q=0.9,en-US;q=0.8,en-CA;q=0.7
HTTP/1.1 200 OK
Date: Mon, 24 Sep 2018 01:23:51 GMT
Server: Apache/2.4.29 (Ubuntu)
Access-Control-Allow-Origin: http://localhost:4200
Access-Control-Allow-Credentials: true
Vary: Origin
Access-Control-Max-Age: 86400
Access-Control-Allow-Headers: 
Content-Length: 0
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8

If HTTP_ORIGIN is present but empty/null

I have an odd condition, using a preview app on the phone (Ionic Creator) to view a project. For some reason it seems to be sending the origin header as below according to my logging:

"HTTP_ORIGIN": ["null"],

When I package the app as an APK it sends no Origin header as you would expect, or if I run it off a webserver it sets a valid one, this android app is mainly for demos prior to actually building the full app and it appears to somehow set the header to empty or null, I can't work out what the middleware is trying to do with that scenario though, I see the logging as below:

When installed as APK (no origin header)
[2017-10-05 04:56:42] slim-app.DEBUG: CORS analysis for request started. [] {"uid":"76cb749"}
[2017-10-05 04:56:42] slim-app.WARNING: Origin header URL cannot be parsed. {"url":"file://"} {"uid":"76cb749"}
[2017-10-05 04:56:42] slim-app.INFO: Request is not CORS (request origin is empty). {"request":null,"server":"[object] (Neomerx\Cors\Http\ParsedUrl: ://)"} {"uid":"76cb749"}
[2017-10-05 04:56:42] slim-app.DEBUG: CORS analysis for request completed. [] {"uid":"76cb749"}

When using the creator android app (empty origin header)
[2017-10-05 04:57:33] slim-app.DEBUG: CORS analysis for request started. [] {"uid":"a2fd7a4"}
[2017-10-05 04:57:33] slim-app.INFO: Request is not CORS (request origin equals to server one). Check config settings for Server Origin. {"request":"[object] (Neomerx\Cors\Http\ParsedUrl: )","server":"[object] (Neomerx\Cors\Http\ParsedUrl: ://)"} {"uid":"a2fd7a4"}
[2017-10-05 04:57:33] slim-app.DEBUG: CORS analysis for request completed. [] {"uid":"a2fd7a4"}

It appears to decide it is not CORS but logs "request origin equals to server one" and I can't find where it is logging that in the source. I'd ideally like to include in the middleware an acceptance of the condition of HTTP_ORIGIN being set but empty, or just to delete the header. I have tried the settings below through trial and error but none seem to let it flow through:

"origin" => [
"null",
"",
null,
,]

Any thoughts?

Multiple origins with wildcards

Hi guys, I read all the code but is not clear to me. This middleware supports multiple origins with wildcards.

Example: [ 'https://*.domain.com', 'https://*.domainmagic.com' ]

Best regards.

Middleware not adding headers on an options/pre-flight call?

Hi,

I am still working on the project I raised and closed an earlier issue for and am now having CORS problems again but struggling to work out why. The summary of what I "think" is happening is:

  • The app is sending a custom header which I haven't managed to find yet on a POST, which causes it to make a pre-flight/options call to check what is allowed. (this is nothing to do with SLIM obviously). I have found that I can 'break' the headers with an incorrect attempt to set them and the calls work ok, SLIM responds to the POST with the correct headers.

  • The SLIM middleware is not responding to the OPTIONS call with the same headers as it is for POST calls, I have the middleware installed and logging so when normal calls come in I see the logs, but I never see anything for a pre-flight call which even if it was responding differently, I'd expect to see as log entry.

Point 1 above I need to sort out myself and am doing so in parallel. Point 2 I would really like some help to work out why SLIM isn't invoking the middleware for a pre-flight call.

More details below the line.


Using Slim 3.8.1 with Cors-Middleware 0.5.2. I'm trying to develop the UI with Ionic (via Ionic Creator) so using AngularJS. Using inspect I can see the headers being set on the POST as below, but the OPTIONS one seems to return different headers:

Response when I allow headers to go correctly and results in an OPTIONS call:

Request URL:https://<mydomain>.azurewebsites.net/logon
Request Method:OPTIONS
Status Code:200 OK
Remote Address:<ip_removed>:443
Referrer Policy:no-referrer-when-downgrade

Response Headers

Allow:OPTIONS, TRACE, GET, HEAD, POST
Content-Length:0
Date:Mon, 02 Oct 2017 12:38:42 GMT
Public:OPTIONS, TRACE, GET, HEAD, POST
Server:Microsoft-IIS/8.0
Set-Cookie:ARRAffinity=71348a31a0fb1ab0cb27394a14e292875f39c36acbe2721cdaa782e123565e70;Path=/;HttpOnly;Domain=<mydomain>.azurewebsites.net
X-Powered-By:ASP.NET

Response below when I 'break' the headers and it doesnt result in a pre-flight check:

Request URL:https://<mydomain>.azurewebsites.net/dashboard
Request Method:POST
Status Code:200 OK
Remote Address:<ip_removed>:443
Referrer Policy:no-referrer-when-downgrade

Response Headers

Access-Control-Allow-Credentials:true
Access-Control-Allow-Origin:https://creator.ionic.io
Access-Control-Expose-Headers:Authorization
Access-Control-Expose-Headers:Accept
Access-Control-Expose-Headers:Content-Type
Access-Control-Expose-Headers:Origin
Cache-Control:no-store, no-cache, must-revalidate
Content-Encoding:gzip
Content-Length:431
Content-Type:application/json;charset=utf-8
Date:Mon, 02 Oct 2017 13:09:51 GMT
Expires:Thu, 19 Nov 1981 08:52:00 GMT
Pragma:no-cache
Server:Microsoft-IIS/8.0
Vary:Origin,Accept-Encoding
X-Powered-By:ASP.NET
X-Powered-By:PHP/7.0.23

Note I know that there are a couple of things I need to hide there once I get to the bottom of this issue such as ASP.NET and PHP but not worried about it at this stage.

I've implemented the middleware as below, I've added a few permitted origins including evil.com due to using browser extension for debugging, I get the issues when that is switched off:

$app->add(new \Tuupola\Middleware\Cors([
  "origin" => ["https://creator.ionic.io", "http://evil.com", "http://<my_domain>.azurewebsites.net", "https://<my_domain>.azurewebsites.net"],
  "methods" => ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
  "headers.allow" => ["Content-Type", "Authorization", "Accept", "Origin"],
  "headers.expose" => ["Content-Type", "Authorization", "Accept", "Origin"],
  "credentials" => true,
  "cache" => 0,
]));

Have I missed something that is obvious in how to make it fire the middleware on the OPTIONS or preflight request?

It appears to me that the lack of any log entry on an OPTIONS and the different headers returned may point to the middleware just not being invoked on an OPTIONS call but its a guess

Upgrading to Slim 4, Running into problems with CORS

First off, I've had great experiences using JWT-Auth and Basic-Auth, and I appreciate your time on these projects, so when I was having some issues with CORS, I was glad to come across this middleware, but I'm still having issues. I've simplified my project quite a bit to try to see if I can't figure out the problem, so here's what I have going on right now:

From my front-end server I'm building the request with the following javascript code:
$.ajax({
method: "GET",
url: "https:test-api:8890/settings/",
contentType: "application/json",
timeout: 5000,
}).done(function (response) {
resolve(response);
}).fail(function (error) {
logError(error, 'Api request ' + method + ' failed to ' + url);
reject(Error('There was an issue interacting with the api'));
});

My backend slim application is as follows:
$app = \Slim\Factory\AppFactory::create();
$app->add(new \Tuupola\Middleware\CorsMiddleware);
$app->addRoutingMiddleware;
$app->get('/settings/, '\Dev\Middleware\UsersController:settings');
$app->run();

When I request GET /settings/ from the front-end server, I receive the following:

  1. Console output of has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
  2. On network traffic, I get no Response headers, and I get an error that "Api request GET failed to /settings/ which is b/c the Ajax Request failed.

Now if I instead write in my .htaccess file the following, everything works fine, so for whatever reason the CorsMiddleware doesn't seem to be attaching those Headers like I'd expect.
Header set Access-Control-Allow-Origin "*"
Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE"
Header set Access-Control-Allow-Headers "Authorization, Content-Type"

I have been running Slim 3 with a custom CorsMiddleware setup but that essentially just adds these headers the same way and it works fine there, so I'm super confused...was hoping this was going to work for me but so far no luck. Thank you for any support you can give. Appreciate it.

Exception causes CORS Error

Whenever an exception is thrown by any of my classes, response loses cors headers. Setting CORS headers manualy within the slim 'errorHandler', solves the issue, but does not feel right.

Cannot set my own header

I have an angular app that use a specific header (name:content) to check it on server,
After configure the corsMiddleware to allow it, I still got an error : Request header field headerMyApp is not allowed by Access-Control-Allow-Headers in preflight response.

I allow it inn my middeware pipeline

$app->pipe(new CorsMiddleware([
            "origin" => ["*"],
            "methods" => ["GET", "POST", "PUT", "PATCH", "DELETE","OPTIONS"],
            "headers.allow" => ["Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With","headerMyApp"],
            "headers.expose" => ["Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With","headerMyApp"]
    ]));

Wrong response object on error handler

Hi,

The response object provided as argument to the error callback has not set the current error state. It's still an empty response with 200 OK.

We are using problem details error response and want to modify the error with the following callback:

function (ServerRequestInterface $request, ResponseInterface $response, $arguments)
use ($problemResponseFactory) {
    $problemDetailsResponse = $problemResponseFactory->createResponse(
        $request,
        $response->getStatusCode(),
        $arguments['message'] ?? $arguments
    );

    return $response
        ->withBody($problemDetailsResponse->getBody())
        ->withHeader('Content-Type', $problemDetailsResponse->getHeader('Content-Type'));
}

But this generates a 500 status as body

HTTP/1.1 401 Unauthorized
Server: swoole-http-server
Connection: keep-alive
Content-Type: application/problem+json
Date: Fri, 22 Feb 2019 12:18:44 GMT
Content-Length: 157

{
    "title": "Internal Server Error",
    "type": "https://httpstatus.es/500",
    "status": 500,
    "detail": "CORS requested method is not supported."
}

... instead of the expected

HTTP/1.1 401 Unauthorized
Server: swoole-http-server
Connection: keep-alive
Content-Type: application/problem+json
Date: Fri, 22 Feb 2019 12:18:44 GMT
Content-Length: 157

{
    "title": "Unauthorized",
    "type": "https://httpstatus.es/401",
    "status": 401,
    "detail": "CORS requested method is not supported."
}

REASON

  1. Your middleware defines the error response AFTER calling the error callback
                return $this->processError($request, $response, [
                    "message" => "CORS request origin is not allowed.",
                ])->withStatus(401);
  1. The problem details generator expects an error code but instead gets a 200 -> this indicates that something unknown went wrong and a 500 Internal Server Error is generated - which makes sense

Woraround
As a workaround we have to hardcode 401 response status but this should be up to your middleware to define.

PHP Fatal error: Class 'Tuupola\Middleware\CorsMiddleware' not found

Hey, I just download your cors-middleware, and this is my middleware.php

`<?php
 // Application middleware

 // e.g: $app->add(new \Slim\Csrf\Guard);
 use Tuupola\Middleware\HttpBasicAuthentication;
  use Tuupola\Middleware\CorsMiddleware;
   $app->add(new Tuupola\Middleware\CorsMiddleware);


      $container = $app->getContainer();
      $container['logger'] = function($c) {
      $logger = new \Monolog\Logger('my_logger');
      $file_handler = new \Monolog\Handler\StreamHandler("../logs/app.log");
      $logger->pushHandler($file_handler);
       return $logger;
       };



        $container["jwt"] = function ($container) {
        return new StdClass;
        };

        $app->add(new \Slim\Middleware\JwtAuthentication([
        "path" => "/",
        "logger" => $container['logger'],
         "secret" => "123456789helo_secret",
         "rules" => [
          //Si se quiere agregar una ruta que no requiere del token
          //Se debe agregar a la siguiente lista
          new \Slim\Middleware\JwtAuthentication\RequestPathRule([
           "path" => "/",
            "passthrough" => ["/usuarios", "/login","/usuario"]
           ]),
          new \Slim\Middleware\JwtAuthentication\RequestMethodRule([
          "passthrough" => ["OPTIONS"]
          ]),
          ],
          "callback" => function ($request, $response, $arguments) use ($container) {
          $container["jwt"] = $arguments["decoded"];
           },
           "error" => function ($request, $response, $arguments) {
           $data["status"] = "error";
           $data["message"] = $arguments["message"];
            return $response
             ->withHeader("Content-Type", "application/json")
             ->write(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
             }
             ]));

             $app->add(new \Slim\Middleware\HttpBasicAuthentication([
             "path" => "/api/token",
             "users" => [
             "user" => "password"
              ]
               ]));



             $app->add(new Tuupola\Middleware\CorsMiddleware([
             "origin" => ["*"],
              "methods" => ["GET", "POST", "PUT", "PATCH", "DELETE"],
              "headers.allow" => [],
              "headers.expose" => [],
              "credentials" => false,
               "cache" => 0,
               ]));
               `

And I get PHP Fatal error: Class 'Tuupola\Middleware\CorsMiddleware' not found any idea what I am doing wrong and how to solve that? Because everytime I make a call to any route i get the error.
By the way, thanks for JWT auth, very helpfull.

`origin.server` Setting Must be Specified with Protocol

Observed

I got the following error:

ERROR: Undefined index: host on line 214 in file [...]/vendor/tuupola/cors-middleware/src/CorsMiddleware.php.

Fix

Change the setting 'origin.server' => 'localhost' to 'origin.server' => 'http://localhost', i.e. make sure to add the protocol.

Behind the Scenes

In /tuupola/cors-middleware/src/CorsMiddleware.php the function determineServerOrigin() calls $url_chunks = parse_url($this->options["origin.server"]);. According to this comment on php.net:

parse_url() DOES NOT parse correctly URLs without scheme or '//'. For example 'www.xyz.com' is considered as a path, not a host.

I was using 'origin.server' => 'localhost'. Then, parse_url() returns an associative array with path as the only key. The function determineServerOrigin() also adds port. But the final returned object $url will not have a host key.

The function buildSettings() in the same file on line 214 expects the object $serverOrigin (which is the return value of determineServerOrigin() and hence the same object described previously) to have a key host.

Hence my setting of declaring the origin.server without protocol breaks the function buildSettings().

Suggested Fix

Merge the default given in determineServerOrigin() into the result given by the rest of the function to ensure the existence of the three keys scheme, host, port.

Alternatively, enforce the origin.server setting to be specified with protocol.

Fatal error: Uncaught TypeError: Return value of Tuupola\Middleware\CorsMiddleware::origin() must be an instance of Tuupola\Middleware\void, none returned

Hello everyone,
I woke up this morning and suddently my api would not work properly as it did yesterday. All my requests returned this error :
Fatal error: Uncaught TypeError: Return value of Tuupola\Middleware\CorsMiddleware::origin() must be an instance of Tuupola\Middleware\void, none returned ... /vendor/tuupola/cors-middleware/src/CorsMiddleware.php on line 165
Here are the ligns from 159 to 165 (where the problem comes from maybe ?) :

 /**
     * Set allowed origin.
     */
    private function origin($origin): void
    {
        $this->options["origin"] = (array) $origin;
    }

If some one has a clue to resolve this it would be great.
Thank you for your time.

PS here is my middleware.php file :

<?php

$app->add(new \Tuupola\Middleware\CorsMiddleware([
    "origin" => ["*"],
    "methods" => ["GET", "POST", "PUT", "PATCH", "DELETE"],
    "headers.allow" => ["Access-Control-Allow-Origin", "Authorization", "Content-Type","Accept"],
    "headers.expose" => [],
    "credentials" => false,
    "cache" => 0,
    "error" => function ($request, $response, $arguments) {
        $data["status"] = "error";
        $data["message"] = $arguments["message"];
        return $response
            ->withHeader("Content-Type", "application/json")
            ->write(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
    },
]));

$app->add(new \Slim\Middleware\JwtAuthentication([
    'secure' => getenv('APP_ENV') !== 'testing',
    'relaxed' => ['localhost', getenv('APP_URL')],
    'path' => '/api',
    'secret' => getenv('JWT_SECRET'),
    'callback' => function ($request, $response, $arguments) use ($container) {
        $email = $arguments['decoded']->sub;
        // auth cache for this authenticated user
        $cache_key = 'au-' . crc32($email);
        // this function obeys the cache default TTL
        $user = $container['cache']->get($cache_key, function () use ($container, $email) {
            // try to find a user with this token...
            $users = new \Application\Models\User();
            $user = $users->findByEmail($email);
            // if we dont find a user, just bail
            if (empty($user)) {
                throw new \Exception('No user found for the given token.', 401);
            }
            $container['logger']->info('fetched fresh user from db');
            return $user[0];
        });

        // assign the user to the container
        $container['user'] = $user;
    },
    'error' => function ($request, $response, $arguments) {
        return $response
            ->withHeader('Content-Type', 'application/json')
            ->write(json_encode([
                'status' => 'error',
                'message' => $arguments['message'],
            ], JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
    },
]));

Core Middleware do not works for me

I am using Slim3

Here is my code to enable CORS Middleware
https://github.com/flextype/flextype/blob/dev/flextype/middlewares.php#L22

and here is my API
https://github.com/flextype/flextype/blob/dev/flextype/api/delivery/entries.php#L29

I am testing my API
https://flextype.org/api/delivery/entries?id=en&token=1a48b9de0494240759c6f85366aaa53d

with https://reqbin.com and I don't see CORS unfortunately.

Server: nginx/1.14.1
Date: Sun, 23 Feb 2020 17:00:17 GMT
Content-Type: application/json
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding
Set-Cookie: PHPSESSID=a666c631f8bf297ae47dc4268ced67cc; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Encoding: gzip

because of this issue, I can't use my API in js app's: https://svelte.dev/repl/e25e8814cc064868b67dad11c90ed029?version=3.19.0

Callable is assumed to be a closure

In a few locations, where is_callable() or the callable type hint is used, the value is assumed to be a closure and fails when the callable uses, e.g., ['class', 'method'] notation. Here are, I think, all of the instances of this:

if (is_callable($this->options["methods"])) {

if (is_callable($methods)) {

if (is_callable($this->options["error"])) {

private function error(callable $error): void

When using is_callable() you can use the third arg to get the callable name for the purposes of calling it, which also will properly call a callable that isn't a closure. So, for example, something like this:

if (is_callable($this->options["methods"], false, $callableName)) {
    $methods = (array) $callableName($request, $response);
} else {
    $methods = $this->options["methods"];
}

For the type hint issue, the type hint can stay the same, then convert (new in 7.1!) to a closure if it isn't a closure. So, for example:

private function error(callable $error): void
{
    if (! $error instanceof Closure) {
        $error = Closure::fromCallable($error);
    }
    $this->options['error'] = $error->bindTo($this);
}

Of course there's a workaround: simply convert to a closure at implementation but that just ain't right.

Debug option

I've noticed logger instance in your source code:

private $logger;

But it seems to me that there is no way to set this logger with options or somewhere else.
This middleware doesn't work with OPTIONS http method on my local Apache(Slim4) server, so it would be great to have any debug option.

psr/http-message 2.x support

I would like to use this package with psr/http-message v 2.x. I could easily provide PR with update to composer.json to allow it, but there is a problem with neomerx/cors-psr7 which seems to be no longer maintained.

In my projects i use my fork which replaces the original package, so adding |^2.0 to composer.json would be enough for me, but keeping the neomerx/cors-psr7 dependency would mean that this package will be never tested against psr/http-message v 2.x.

What are your thoughts about this package future? Are you willing switch to some neomerx/cors-psr7 fork or fork it yourself?

CORS issues with The PHP League Route

I'm having CORS issues using the https://route.thephpleague.com/ library

My current config: https://gist.github.com/guilherme90/f8cfd339fbdf2562013030d679f971a8

cURL with OPTIONS

curl -X OPTIONS "http://127.0.0.1:8000/api/contacts/1" --include --header "Access-Control-Request-Method: OPTIONS" --header  "Origin: http://10.0.0.184:8080"

Response cURL

HTTP/1.1 200 OK
Host: 127.0.0.1:8000
Date: Wed, 11 Mar 2020 19:14:38 GMT
Connection: close
X-Powered-By: PHP/7.3.9
Access-Control-Allow-Origin: http://10.0.0.184:8080
Vary: Origin
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Content-type: text/html; charset=UTF-8

cURL with GET

curl "http://127.0.0.1:8000/api/contacts/1" --include --header "Access-Control-Request-Method: OPTIONS" --header "Origin: http://10.0.0.184:8080"

Response cURL

HTTP/1.1 200 OK
Host: 127.0.0.1:8000
Date: Wed, 11 Mar 2020 19:19:02 GMT
Connection: close
X-Powered-By: PHP/7.3.9
Content-Type: application/json
Access-Control-Allow-Origin: http://10.0.0.184:8080
Vary: Origin

{"data":[], "offset":0,"currentPage":1,"perPage":20,"pages":0,"total":0}

But when I apply with Axios, gives me this problem. Any idea about that ?
Thanks

No Access-Control-Allow-Method in the header

image
My cors config is like this:

$container["Cors"] = function ($container) {
    return new Cors([
        "logger" => $container["logger"],
        "origin" => ["*"],
        "methods" => ["GET", "POST", "PUT", "PATCH", "DELETE"],
        "headers.allow" => ["Authorization", "If-Match", "If-Unmodified-Since"],
        "headers.expose" => ["Authorization", "Etag"],
        "credentials" => true,
        "cache" => 60,
        "error" => function ($request, $response, $arguments) {
            $data["status"] = "error";
            $data["message"] = $arguments["message"];
            return $response
                ->withHeader("Content-Type", "application/json")
                ->write(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
        }
    ]);
};

Why there is no Access-Control-Allow-Method in the header?

Fatal error: Uncaught Error: Class not found.

I tried to install this plugin but I got this error:

Fatal error: Uncaught Error: Class 'Slim\Middleware\CorsMiddleware' not found in.

How I solve this

files:

-> apirestful
-> src
middleware.php
-> vendor
-> tuupola
-> cors-middleware


And my middleware.php:

<?php

use Tuupola\Middleware\CorsMiddleware;

$settings = $app->getContainer()->get('settings');

$app->add(new \Tuupola\Middleware\CorsMiddleware);

$app->add(new \Slim\Middleware\JwtAuthentication([
    "path" => "/api", /* or ["/api", "/admin"] */
    "secret" => $settings['jwt']['secret'],
    "algorithm" => ["HS256"],
    "callback" => function ($request, $response, $arguments) use ($container) {
        $container["jwt"] = $arguments["decoded"];
    },
    "error" => function ($request, $response, $arguments) {
        $data["status"] = "error";
        $data["message"] = $arguments["message"];
        return $response
            ->withHeader("Content-Type", "application/json")
            ->write(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
    }
]));
?>

composer.json

"require": {
    "php": ">=5.5.0",
    "slim/slim": "^3.9",
    "slim/php-view": "^2.0",
    "monolog/monolog": "^1.17",
    "tuupola/slim-jwt-auth": "^2.3",
    "tuupola/base62": "^0.9.0",
    "tuupola/cors-middleware": "^0.5.2"
},

CorsMiddleware is now generic - by accident?

Since version 1.4.0, CorsMiddleware is now generic because @template was added to the class PHPDoc. That means all users of the class must specify template parameter TSanitizedOptions when using a static analyzer like PHPStan above a certain level (i.e. with generics support/checks enabled).

I was unable to find an example how/why this template parameter should be specified and what the reason and anticipated benefit was of making CorsMiddleware generic. I suspect that this was done by accident and what you really wanted is to define and use local type aliases:
https://phpstan.org/writing-php-code/phpdoc-types#local-type-aliases

Example of how the current implementation results in a PHPStan error that was not present before:
https://phpstan.org/r/381821ff-52e4-491d-afc8-4832e219d28b

Cors middleware only being called on GET - not POST?

Hi Mika,

Thanks for all the tireless work you put into these repos, I'm currently using your CORS-Middleware and planning to use the JWT or BRANCA on in due course.

I have a really annoying issue which I'm sure is an easy fix but I can't find it, using Slim 3.8.1 with Cors-Middleware 0.5.2 I am getting CORS errors on the POST but not GET calls. I'm trying to develop the UI with Ionic (via Ionic Creator) so using AngularJS. Using inspect I can see the headers being set on the GET as below, GET doesnt appear to issue an OPTIONS call, not sure if that's correct or not:

Access-Control-Allow-Credentials:true
Access-Control-Allow-Origin:https://creator.ionic.io
Cache-Control:no-store, no-cache, must-revalidate
Content-Length:73519
Content-Type:application/json,text/html; charset=UTF-8
Date:Fri, 15 Sep 2017 20:16:29 GMT
Expires:Thu, 19 Nov 1981 08:52:00 GMT
Pragma:no-cache
Server:Microsoft-IIS/8.0
Vary:Origin
X-Powered-By:ASP.NET
X-Powered-By:PHP/7.0.21

But using POST I can see them being set as below on the OPTIONS call:

Allow:OPTIONS, TRACE, GET, HEAD, POST
Content-Length:0
Date:Fri, 15 Sep 2017 20:16:30 GMT
Public:OPTIONS, TRACE, GET, HEAD, POST
Server:Microsoft-IIS/8.0
Set-Cookie:ARRAffinity=a544f2c7a222ce45b9597862ebbf6fc1d1b2ee338f4bf5d2d1aeb2c33eb0086d;Path=/;HttpOnly;Domain=<my_domain>.azurewebsites.net
X-Powered-By:ASP.NET

I've implemented the middleware as below, I've added a few permitted origins including evil.com due to using browser extension for debugging, I get the issues when that is switched off:

$app->add(new \Tuupola\Middleware\Cors([
  "origin" => ["https://creator.ionic.io", "http://evil.com", "http://<my_domain>.azurewebsites.net", "https://<my_domain>.azurewebsites.net"],
  "methods" => ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
  "headers.allow" => ["Content-Type", "Authorization", "Accept", "Origin"],
  "headers.expose" => [],
  "credentials" => true,
  "cache" => 0,
]));

Have I missed something that is obvious in how to make it fire the middleware on the OPTIONS or preflight request?

Thanks for any help you can offer, I've been using SLIM for a while but not much on middleware yet, also first time I've hit CORS pain. My plan for production is to deploy API to one server, UI to another for use on mobile browsers and then a mobile app that will go straight to the middleware.

Any thoughts most welcome!
Thanks

Improve CORS error debugging by populating cors headers that did not fail the check

Scenario

  1. User agent sends preflight request with requested methods and headers.
  2. One of the headers fails the check, but the origin is correct, the methods are correct too
  3. CORS middleware returns 401
  4. User agent fails with message like Reason: CORS header ‘Access-Control-Allow-Origin’ missing

So it's rather hard to tell what the problem is. It's not that the origin check failed, but that the header check failed.

The person trying to discover the issue should instead be directed to the missing header error
👉 https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSMissingAllowHeaderFromPreflight.

Workaround

Browsers do not display the response's content, so sending the information as the response's data is mostly useless.

The only way of propagating the error message to the browser that I can think of is using a header, like this:

// CORS middleware error handler example
function (Request $request, Response $response, array $arguments): Response {
    return $response
        ->withAddedHeader('X-CORS-Error-Message', $arguments['message'] ?? 'Generic CORS error.');
},

Future

I am writing this down to let others know about this possibility of conveying the message,
and to ask if it was possible to implement partial response decoration, with headers that did not fail, so that the browser could generate a correct error message.

Issue with comma-separated headers value in MS Edge

Microsoft Edge requires the access-control-expose-headers headers to be lowercase and have no spaces between the comma-separated values. The CORS-middleware package (v0.5.2) automatically adds spaces to the the values being returned in the response headers.

This results in an issue preventing JavaScript to read the exposed headers when the browser is MS Edge.

Reference to the issue:
https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12046299/

Edit: fixed the link to the issue.

Failing tests

PHPstan is complaining about several unused private methods:

 ------ ---------------------------------------------------------------------- 
  Line   CorsMiddleware.php                                                    
 ------ ---------------------------------------------------------------------- 
  295    Method Tuupola\Middleware\CorsMiddleware::origin() is unused.         
  304    Method Tuupola\Middleware\CorsMiddleware::methods() is unused.        
  320    Method Tuupola\Middleware\CorsMiddleware::headersAllow() is unused.   
  328    Method Tuupola\Middleware\CorsMiddleware::headersExpose() is unused.  
  336    Method Tuupola\Middleware\CorsMiddleware::credentials() is unused.    
  344    Method Tuupola\Middleware\CorsMiddleware::originServer() is unused.   
  352    Method Tuupola\Middleware\CorsMiddleware::cache() is unused.          
  360    Method Tuupola\Middleware\CorsMiddleware::error() is unused.          
  372    Method Tuupola\Middleware\CorsMiddleware::logger() is unused.         
 ------ ---------------------------------------------------------------------- 

Should the methods be removed or made public?

Provide an example for dynamically specifying allowed methods for Slim 4

I've trouble dynamically specifying the allowed methods while using Slim4 like you did in the readme file for Slim3

use Fastroute\Dispatcher;
use Tuupola\Middleware\CorsMiddleware;

$app->add(
    new CorsMiddleware([
        "origin" => ["*"],
        "methods" => function($request) use ($app) {
            $container = $app->getContainer();
            $dispatch = $container["router"]->dispatch($request);
            if (Dispatcher::METHOD_NOT_ALLOWED === $dispatch[0]) {
                return $dispatch[1];
            }
        }
    ])
);

I've tried the following for Slim 4:

$app->addMiddleware(new Tuupola\Middleware\CorsMiddleware([
  'methods' => function ($request): array {
	$routeContext = RouteContext::fromRequest($request);
	$routingResults = $routeContext->getRoutingResults();
	$methods = $routingResults->getAllowedMethods();
	
	return $methods;
  },
]));

But either when I put $app->addRoutingMiddleware(); in front of the code block above I get the following error:

PHP Fatal error:  Uncaught RuntimeException: Cannot create RouteContext before routing has been completed in /var/www/html/vendor/slim/slim/Slim/Routing/RouteContext.php:40
Stack trace:
#0 /var/www/html/public/index.php(103): Slim\\Routing\\RouteContext::fromRequest()
#1 /var/www/html/vendor/tuupola/cors-middleware/src/CorsMiddleware.php(175): Closure->{closure}()
#2 /var/www/html/vendor/tuupola/cors-middleware/src/CorsMiddleware.php(91): Tuupola\\Middleware\\CorsMiddleware->buildSettings()
#3 /var/www/html/vendor/slim/slim/Slim/MiddlewareDispatcher.php(147): Tuupola\\Middleware\\CorsMiddleware->process()
#4 /var/www/html/vendor/slim/slim/Slim/MiddlewareDispatcher.php(81): Psr\\Http\\Server\\RequestHandlerInterface@anonymous->handle()
#5 /var/www/html/vendor/slim/slim/Slim/App.php(215): Slim\\MiddlewareDispatcher->handle()
#6 /var/www/html/vendor/slim/slim/Slim/App.php(199): Slim\\App->handle()
#7 /var/www/html/public/index.php(127): Slim\\App->run()
#8 {main}
  thrown in /var/www/html/vendor/slim/slim/Slim/Routing/RouteContext.php on line 40

or when I put $app->addRoutingMiddleware(); after the code block, the Cors middleware doesn't get invoked.

I can mitigate the first error where it complains about the RouteContext when I implement a OPTIONS catch-all block like so:

$app->options('/{routes:.+}', function (ServerRequestInterface $request, ResponseInterface $response, $args): ResponseInterface {
  return $response;
});

But I wonder if I can get rid of the OPTIONS catch-all block altogether, what I'm missing here?

Fatal error: Uncaught Error: Call to undefined method Slim\Slim::pipe()

Cors Middleware is normally installed with composer.

My index.php of API is:


<?php

$loader = require 'vendor/autoload.php';


//Consults Routes
$app = new \Slim\Slim(array(
    'templates.path' => 'templates'
));


use Tuupola\Middleware\CorsMiddleware;

#$app->pipe(ImplicitOptionsMiddleware::class);
$app->pipe(CorsMiddleware::class);



$app->add(new Tuupola\Middleware\CorsMiddleware([
    "origin" => ["*"],
    "methods" => ["GET", "POST", "PUT", "PATCH", "DELETE"],
    "headers.allow" => [],
    "headers.expose" => [],
    "credentials" => false,
    "cache" => 0,
]));


$app->get('/consults/', function() use ($app){
	(new \controllers\Consults($app))->list();
});

$app->get('/consults/:id', function($id) use ($app){
	(new \controllers\Consults($app))->get($id);
});

$app->post('/consults/', function() use ($app){
	(new \controllers\Consults($app))->new();
});

$app->put('/consults/:id', function($id) use ($app){
	(new \controllers\Consults($app))->edit($id);
});

$app->delete('/consults/:id', function($id) use ($app){
	(new \controllers\Consults($app))->delete($id);
});



//Vehicles Routes
$app->get('/vehicles/', function() use ($app){
	(new \controllers\Vehicles($app))->list();
});

$app->get('/vehicles/:id', function($id) use ($app){
	(new \controllers\Vehicles($app))->get($id);
});

$app->get('/vehicles-top/', function() use ($app){
	(new \controllers\Vehicles($app))->listTop();
});

$app->post('/vehicles/', function() use ($app){
	(new \controllers\Vehicles($app))->new();
});

$app->put('/vehicles/:id', function($id) use ($app){
	(new \controllers\Vehicles($app))->edit($id);
});

$app->delete('/vehicles/:id', function($id) use ($app){
	(new \controllers\Vehicles($app))->delete($id);
});

$app->put('/vehicles/:id', function($id) use ($app){
	(new \controllers\Vehicles($app))->incrementConsult($id);
});

$app->run();

I have a error with the line

$app->pipe(CorsMiddleware::class);

Fatal error: Uncaught Error: Call to undefined method Slim\Slim::pipe() in C:\wamp64\www\tabela-fipe\backend\tabelafipe-backend\api\index.php on line 15

Is slim the problem here?

OPTIONS request gets a "401 Unauthorized" response

I use the following configuration for setting up CORS.

$app->add(new \Tuupola\Middleware\Cors([
    "origin" => ["*"],
    "methods" => ["GET", "POST", "PUT", "PATCH", "DELETE"],
    "headers.allow" => [],
    "headers.expose" => [],
    "credentials" => false,
    "cache" => 0,
]));

When I send a POST request with AngularJS, first a preflight OPTIONS is sent. Then I got stuck with a 401 Unauthorized response. So, POST request can never be sent.

I tried adding "OPTIONS" to into methods parameter in the configuration, but nothing changes. Am I missing something or doing it wrong?

Access-Control-Allow-Headers isn't being returned by the preflight request?

The preflight request returns with a status of 200 OK, the problem is the header "Access-Control-Allow-Headers" isn't being returned so when the real request is made the browser doesn't send along the headers I want it to. I also tested this using curl and terminal, and if I send the real request with curl and the custom headers everything works like it's supposed to so it has something to do with this library. Are there certain circumstances where "Access-Control-Allow-Headers" isn't returned?

$cors = new \Tuupola\Middleware\Cors([
    "origin" => ["https://test.myapp.com"],
    "methods" => ["GET", "POST", "PUT", "PATCH", "DELETE"],
    "headers.allow" => ["Referer", "User-Agent", "Authorization", "X-Requested-With", "Origin", "Headers", "Method", "Content-Type", "Accept", "X-CSRF-TOKEN"],
    "headers.expose" => ["Referer", "User-Agent", "Authorization", "X-Requested-With", "Origin", "Headers", "Method", "Content-Type", "Accept", "X-CSRF-TOKEN"],
    "credentials" => true,
    "cache" => 0,
    "error" => function ($request, $response, $arguments) {
        $data["status"] = "error";
        $data["message"] = $arguments["message"];
        return $response
            ->withHeader("Content-Type", "application/json")
            ->write(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
    }
]);
$app->add($cors);

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.