diff --git a/core/core.services.yml b/core/core.services.yml
index c5a13a4..62a0f3e 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -1022,6 +1022,11 @@ services:
     tags:
       - { name: event_subscriber }
     arguments: ['@router', '@request_stack', '@router.request_context', NULL]
+  options_request_listener:
+    class: Drupal\Core\EventSubscriber\OptionsRequestSubscriber
+    arguments: ['@router.route_provider']
+    tags:
+      - { name: event_subscriber }
   bare_html_page_renderer:
     class: Drupal\Core\Render\BareHtmlPageRenderer
     arguments: ['@renderer', '@html_response.attachments_processor']
diff --git a/core/lib/Drupal/Core/EventSubscriber/OptionsRequestSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/OptionsRequestSubscriber.php
new file mode 100644
index 0000000..622fe23
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/OptionsRequestSubscriber.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\EventSubscriber\OptionsRequestSubscriber.
+ */
+
+namespace Drupal\Core\EventSubscriber;
+
+use Symfony\Cmf\Component\Routing\RouteProviderInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Handles options requests.
+ *
+ * Therefore it sends a options response using all methods on all possible
+ * routes.
+ */
+class OptionsRequestSubscriber implements EventSubscriberInterface {
+
+  /**
+   * @var \Symfony\Cmf\Component\Routing\RouteProviderInterface
+   */
+  protected $routeProvider;
+
+  /**
+   * Creates a new OptionsRequestSubscriber instance.
+   *
+   * @param \Symfony\Cmf\Component\Routing\RouteProviderInterface $routeProvider
+   *   THe route provider.
+   */
+  public function __construct(RouteProviderInterface $routeProvider) {
+    $this->routeProvider = $routeProvider;
+  }
+
+  /**
+   * Tries to handle the options request.
+   *
+   * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
+   *   The request event.
+   */
+  public function onRequest(GetResponseEvent $event) {
+    if ($event->getRequest()->isMethod('OPTIONS')) {
+      $routes = $this->routeProvider->getRouteCollectionForRequest($event->getRequest());
+      // In case we don't have any routes, a 403 should be thrown by the normal
+      // request handling.
+      if (count($routes) > 0) {
+        $methods = array_map(function (Route $route) {
+          return $route->getMethods();
+        }, $routes->all());
+        // Flatten and unique the available methods.
+        $methods = array_unique(call_user_func_array('array_merge', $methods));
+        $response = new Response('', 200, ['Allow' => implode(', ', $methods)]);
+        $event->setResponse($response);
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    // Set a high priority so its executed before routing.
+    $events[KernelEvents::REQUEST][] = ['onRequest', 1000] ;
+    return $events;
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/EventSubscriber/OptionsRequestSubscriberTest.php b/core/tests/Drupal/Tests/Core/EventSubscriber/OptionsRequestSubscriberTest.php
new file mode 100644
index 0000000..f1c6296
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/EventSubscriber/OptionsRequestSubscriberTest.php
@@ -0,0 +1,114 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\EventSubscriber\OptionsRequestSubscriberTest.
+ */
+
+namespace Drupal\Tests\Core\EventSubscriber;
+
+use Drupal\Core\EventSubscriber\OptionsRequestSubscriber;
+use Symfony\Cmf\Component\Routing\RouteProviderInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * @coversDefaultClass \Drupal\Core\EventSubscriber\OptionsRequestSubscriber
+ * @group EventSubscriber
+ */
+class OptionsRequestSubscriberTest extends \PHPUnit_Framework_TestCase {
+
+  /**
+   * @covers ::onRequest
+   */
+  public function testWithNonOptionRequest() {
+    $kernel = $this->prophesize(HttpKernelInterface::class);
+    $request = Request::create('/example', 'GET');
+
+    $route_provider = $this->prophesize(RouteProviderInterface::class);
+    $route_provider->getRouteCollectionForRequest($request)->shouldNotBeCalled();
+
+    $subscriber = new OptionsRequestSubscriber($route_provider->reveal());
+    $event = new GetResponseEvent($kernel->reveal(), $request, HttpKernelInterface::MASTER_REQUEST);
+    $subscriber->onRequest($event);
+
+    $this->assertFalse($event->hasResponse());
+  }
+
+  /**
+   * @covers ::onRequest
+   */
+  public function testWithoutMatchingRoutes() {
+    $kernel = $this->prophesize(HttpKernelInterface::class);
+    $request = Request::create('/example', 'OPTIONS');
+
+    $route_provider = $this->prophesize(RouteProviderInterface::class);
+    $route_provider->getRouteCollectionForRequest($request)->willReturn(new RouteCollection())->shouldBeCalled();
+
+    $subscriber = new OptionsRequestSubscriber($route_provider->reveal());
+    $event = new GetResponseEvent($kernel->reveal(), $request, HttpKernelInterface::MASTER_REQUEST);
+    $subscriber->onRequest($event);
+
+    $this->assertFalse($event->hasResponse());
+  }
+
+  /**
+   * @covers ::onRequest
+   * @dataProvider providerTestOnRequestWithOptionsRequest
+   */
+  public function testWithOptionsRequest(RouteCollection $collection, $expected_header) {
+    $kernel = $this->prophesize(HttpKernelInterface::class);
+    $request = Request::create('/example', 'OPTIONS');
+
+    $route_provider = $this->prophesize(RouteProviderInterface::class);
+    $route_provider->getRouteCollectionForRequest($request)->willReturn($collection)->shouldBeCalled();
+
+    $subscriber = new OptionsRequestSubscriber($route_provider->reveal());
+    $event = new GetResponseEvent($kernel->reveal(), $request, HttpKernelInterface::MASTER_REQUEST);
+    $subscriber->onRequest($event);
+
+    $this->assertTrue($event->hasResponse());
+    $response = $event->getResponse();
+    $this->assertEquals(200, $response->getStatusCode());
+    $this->assertEquals($expected_header, $response->headers->get('Allow'));
+  }
+
+  public function providerTestOnRequestWithOptionsRequest() {
+    $data = [];
+
+    foreach (['GET', 'POST', 'PATCH', 'PUT', 'DELETE'] as $method) {
+      $collection = new RouteCollection();
+      $collection->add('example.1', new Route('/example', [], [], [], '', [], [$method]));
+      $data['one_route_' . $method] = [$collection, $method];
+    }
+
+    foreach (['GET', 'POST', 'PATCH', 'PUT', 'DELETE'] as $method_a) {
+      foreach (['GET', 'POST', 'PATCH', 'PUT', 'DELETE'] as $method_b) {
+        if ($method_a != $method_b) {
+          $collection = new RouteCollection();
+          $collection->add('example.1', new Route('/example', [], [], [], '', [], [$method_a, $method_b]));
+          $data['one_route_' . $method_a . '_' . $method_b] = [$collection, $method_a . ', ' . $method_b];
+        }
+      }
+    }
+
+    foreach (['GET', 'POST', 'PATCH', 'PUT', 'DELETE'] as $method_a) {
+      foreach (['GET', 'POST', 'PATCH', 'PUT', 'DELETE'] as $method_b) {
+        foreach (['GET', 'POST', 'PATCH', 'PUT', 'DELETE'] as $method_c) {
+          $collection = new RouteCollection();
+          $collection->add('example.1', new Route('/example', [], [], [], '', [], [$method_a]));
+          $collection->add('example.2', new Route('/example', [], [], [], '', [], [$method_a, $method_b]));
+          $collection->add('example.3', new Route('/example', [], [], [], '', [], [$method_b, $method_c]));
+          $methods = array_unique([$method_a, $method_b, $method_c]);
+          $data['multiple_routes_' . $method_a . '_' . $method_b . '_' . $method_c] = [$collection,  implode(', ', $methods)];
+        }
+      }
+    }
+
+    return $data;
+  }
+
+}
