diff --git a/core/lib/Drupal/Core/Access/AccessCheckInterface.php b/core/lib/Drupal/Core/Access/AccessCheckInterface.php
new file mode 100644
index 0000000..a675f41
--- /dev/null
+++ b/core/lib/Drupal/Core/Access/AccessCheckInterface.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Access\AccessCheckInterface.
+ */
+
+namespace Drupal\Core\Access;
+
+use Symfony\Component\Routing\Route;
+
+/**
+ * A access check service determines access rules for particular routes.
+ */
+interface AccessCheckInterface {
+
+  /**
+   * Declares whether the access check applies to a specific route or not.
+   *
+   * @param Symfony\Component\Routing\Route $route
+   *   The route to consider attaching to.
+   *
+   * @return bool
+   *   TRUE if the check applies to the passed route, FALSE otherwise.
+   */
+  public function applies(Route $route);
+
+  /**
+   * Checks for access to route.
+   *
+   * @param Symfony\Component\Routing\Route $route
+   *   The route to check against.
+   *
+   * @return mixed
+   *   TRUE if access is allowed.
+   *   FALSE if not.
+   *   NULL if no opinion.
+   */
+  public function access(Route $route);
+}
diff --git a/core/lib/Drupal/Core/Access/AccessManager.php b/core/lib/Drupal/Core/Access/AccessManager.php
new file mode 100644
index 0000000..d2cab6a
--- /dev/null
+++ b/core/lib/Drupal/Core/Access/AccessManager.php
@@ -0,0 +1,133 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\Core\Access\AccessManager.
+ */
+
+namespace Drupal\Core\Access;
+
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\DependencyInjection\ContainerAware;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+
+/**
+ * Attaches access checks services to routes and runs them on request.
+ */
+class AccessManager extends ContainerAware {
+
+  /**
+   * Array of registered access check service ids.
+   *
+   * @var array
+   */
+  protected $checkIds;
+
+  /**
+   * Array of access check objects keyed by service id.
+   *
+   * @var array
+   */
+  protected $checks;
+
+  /**
+   * Registers a new AccessCheck by service ID.
+   *
+   * @param string $service_id
+   *   The ID of the service in the Container that provides a check.
+   */
+  public function addCheckService($service_id) {
+    $this->checkIds[] = $service_id;
+  }
+
+  /**
+   * For each route, saves a list of applicable access checks to the route.
+   *
+   * @param RouteCollection $routes
+   *   A collection of routes to apply checks to.
+   */
+  public function setChecks(RouteCollection $routes) {
+    foreach ($routes as $route) {
+      $checks = $this->applies($route);
+      if (!empty($checks)) {
+        $route->setOption('_access_checks', $checks);
+      }
+    }
+  }
+
+  /**
+   * Determine which registered access checks apply to a route.
+   *
+   * @param Symfony\Component\Routing\Route $route
+   *   The route to get list of access checks for.
+   *
+   * @return array
+   *   An array of service ids for the access checks that apply to passed
+   *   route.
+   */
+  protected function applies(Route $route) {
+    $checks = array();
+
+    foreach ($this->checkIds as $service_id) {
+      if (empty($this->checks[$service_id])) {
+        $this->loadCheck($service_id);
+      }
+
+      if ($this->checks[$service_id]->applies($route)) {
+        $checks[] = $service_id;
+      }
+    }
+
+    return $checks;
+  }
+
+  /**
+   * Checks a route against applicable access check services.
+   *
+   * Determines whether the route is accessible or not.
+   *
+   * @param Symfony\Component\Routing\Route $route
+   *   The route to check access to.
+   *
+   * @throws Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
+   *   If any access check denies access or none explicitly approve.
+   */
+  public function check(Route $route) {
+    $access = FALSE;
+    $checks = $route->getOption('_access_checks') ?: array();
+
+    // No checks == deny by default.
+    foreach ($checks as $service_id) {
+      if (empty($this->checks[$service_id])) {
+        $this->loadCheck($service_id);
+      }
+
+      $access = $this->checks[$service_id]->access($route);
+      if ($access === FALSE) {
+        // A check has denied access, no need to continue checking.
+        break;
+      }
+    }
+
+    // Access has been denied or not explicily approved.
+    if (!$access) {
+      throw new AccessDeniedHttpException();
+    }
+  }
+
+  /**
+   * Lazy-loads access check services.
+   *
+   * @param string $service_id
+   *   The service id of the access check service to load.
+   */
+  protected function loadCheck($service_id) {
+    if (!in_array($service_id, $this->checkIds)) {
+      throw new \InvalidArgumentException(sprintf('No check has been registered for %s', $service_id));
+    }
+
+    $this->checks[$service_id] = $this->container->get($service_id);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Access/DefaultAccessCheck.php b/core/lib/Drupal/Core/Access/DefaultAccessCheck.php
new file mode 100644
index 0000000..7904656
--- /dev/null
+++ b/core/lib/Drupal/Core/Access/DefaultAccessCheck.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Access\DefaultAccessCheck.
+ */
+
+namespace Drupal\Core\Access;
+
+use Symfony\Component\Routing\Route;
+
+/**
+ * Allows access to routes to be controlled by an '_access' boolean parameter.
+ */
+class DefaultAccessCheck implements AccessCheckInterface {
+
+  /**
+   * Implements Drupal\Core\Access\AccessCheckInterface::applies().
+   */
+  public function applies(Route $route) {
+    // The route will either fold to boolean true or false, so just return that.
+    return (boolean)$route->getRequirement('_access');
+  }
+
+  /**
+   * Implements Drupal\Core\Access\AccessCheckInterface::access().
+   */
+  public function access(Route $route) {
+    return $route->getRequirement('_access');
+  }
+}
diff --git a/core/lib/Drupal/Core/Access/PermissionAccessCheck.php b/core/lib/Drupal/Core/Access/PermissionAccessCheck.php
new file mode 100644
index 0000000..f89a423
--- /dev/null
+++ b/core/lib/Drupal/Core/Access/PermissionAccessCheck.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Access\PermissionAccessCheck.
+ */
+
+namespace Drupal\Core\Access;
+
+use Symfony\Component\Routing\Route;
+
+/**
+ * Determines access to routes based on permissions defined via hook_permission().
+ */
+class PermissionAccessCheck implements AccessCheckInterface {
+
+  /**
+   * Implements Drupal\Core\Access\AccessCheckInterface::applies().
+   */
+  public function applies(Route $route) {
+    return $route->hasDefault('_permission');
+  }
+
+  /**
+   * Implements Drupal\Core\Access\AccessCheckInterface::access().
+   */
+  public function access(Route $route) {
+    $permission = $route->getDefault('_permission');
+    // @todo Replace user_access() with a correctly injected and session-using
+    //   alternative.
+    // If user_access() fails, return NULL to give other checks a chance.
+    return user_access($permission) ? TRUE : NULL;
+  }
+}
diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php
index bbc4e2e..d4c0b94 100644
--- a/core/lib/Drupal/Core/CoreBundle.php
+++ b/core/lib/Drupal/Core/CoreBundle.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core;
 
 use Drupal\Core\DependencyInjection\Compiler\RegisterKernelListenersPass;
+use Drupal\Core\DependencyInjection\Compiler\RegisterAccessChecksPass;
 use Drupal\Core\DependencyInjection\Compiler\RegisterMatchersPass;
 use Drupal\Core\DependencyInjection\Compiler\RegisterNestedMatchersPass;
 use Drupal\Core\DependencyInjection\Compiler\RegisterSerializationClassesPass;
@@ -72,7 +73,9 @@ public function build(ContainerBuilder $container) {
       ->addArgument(new Reference('database'));
     $container->register('router.builder', 'Drupal\Core\Routing\RouteBuilder')
       ->addArgument(new Reference('router.dumper'))
-      ->addArgument(new Reference('lock'));
+      ->addArgument(new Reference('lock'))
+      ->addArgument(new Reference('dispatcher'));
+
 
     $container->register('matcher', 'Drupal\Core\Routing\ChainMatcher');
     $container->register('legacy_url_matcher', 'Drupal\Core\LegacyUrlMatcher')
@@ -103,8 +106,17 @@ public function build(ContainerBuilder $container) {
     $container->register('view_subscriber', 'Drupal\Core\EventSubscriber\ViewSubscriber')
       ->addArgument(new Reference('content_negotiation'))
       ->addTag('event_subscriber');
+    $container->register('legacy_access_subscriber', 'Drupal\Core\EventSubscriber\LegacyAccessSubscriber')
+      ->addTag('event_subscriber');
+    $container->register('access_manager', 'Drupal\Core\Access\AccessManager')
+      ->addMethodCall('setContainer', array(new Reference('service_container')));
     $container->register('access_subscriber', 'Drupal\Core\EventSubscriber\AccessSubscriber')
+      ->addArgument(new Reference('access_manager'))
       ->addTag('event_subscriber');
+    $container->register('access_check.default', 'Drupal\Core\Access\DefaultAccessCheck')
+      ->addTag('access_check');
+    $container->register('access_check.permission', 'Drupal\Core\Access\PermissionAccessCheck')
+      ->addTag('access_check');
     $container->register('maintenance_mode_subscriber', 'Drupal\Core\EventSubscriber\MaintenanceModeSubscriber')
       ->addTag('event_subscriber');
     $container->register('path_subscriber', 'Drupal\Core\EventSubscriber\PathSubscriber')
@@ -138,6 +150,7 @@ public function build(ContainerBuilder $container) {
     $container->addCompilerPass(new RegisterKernelListenersPass(), PassConfig::TYPE_AFTER_REMOVING);
     // Add a compiler pass for adding Normalizers and Encoders to Serializer.
     $container->addCompilerPass(new RegisterSerializationClassesPass());
+    $container->addCompilerPass(new RegisterAccessChecksPass());
   }
 
 }
diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterAccessChecksPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterAccessChecksPass.php
new file mode 100644
index 0000000..a69e35a
--- /dev/null
+++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterAccessChecksPass.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\DependencyInjection\Compiler\RegisterAccessChecksPass.
+ */
+
+namespace Drupal\Core\DependencyInjection\Compiler;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+
+class RegisterAccessChecksPass implements CompilerPassInterface {
+
+  /**
+   * Implements Symfony\Component\DependencyInjection\Compiler::process().
+   *
+   * Adds services tagged 'access_check' to the access_manaher service.
+   */
+  public function process(ContainerBuilder $container) {
+    if (!$container->hasDefinition('access_manager')) {
+      return;
+    }
+    $access_manager = $container->getDefinition('access_manager');
+    foreach ($container->findTaggedServiceIds('access_check') as $id => $attributes) {
+      $access_manager->addMethodCall('AddCheckService', array($id));
+    }
+  }
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php
index 4f1dc75..d97f392 100644
--- a/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/AccessSubscriber.php
@@ -11,6 +11,9 @@
 use Symfony\Component\HttpKernel\Event\GetResponseEvent;
 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Drupal\Core\Routing\RoutingEvents;
+use Drupal\Core\Access\AccessManager;
+use Drupal\Core\Routing\RouteBuildEvent;
 
 /**
  * Access subscriber for controller requests.
@@ -18,21 +21,41 @@
 class AccessSubscriber implements EventSubscriberInterface {
 
   /**
-   * Verifies that the current user can access the requested path.
+   * Constructs a new AccessCheckManager.
    *
-   * @todo This is a total hack to keep our current access system working. It
-   *   should be replaced with something robust and injected at some point.
+   * @param AccessCheckManager $access_check_manager
+   *   The access check manager that will be responsible for applying
+   *   AccessCheckers against routes.
+   */
+  public function __construct(AccessManager $access_manager) {
+    $this->accessManager = $access_manager;
+  }
+
+  /**
+   * Verifies that the current user can access the requested path.
    *
    * @param Symfony\Component\HttpKernel\Event\GetResponseEvent $event
    *   The Event to process.
    */
   public function onKernelRequestAccessCheck(GetResponseEvent $event) {
+    $request = $event->getRequest();
+    if (!$request->attributes->has('_route')) {
+      // If no Route is available it is likely a static resource and access is
+      // handled elsewhere.
+      return;
+    }
 
-    $router_item = $event->getRequest()->attributes->get('drupal_menu_item');
+    $this->accessManager->check($request->attributes->get('_route'));
+  }
 
-    if (isset($router_item['access']) && !$router_item['access']) {
-      throw new AccessDeniedHttpException();
-    }
+  /**
+   * Apply access checks to routes.
+   *
+   * @param Drupal\Core\Routing\RouteBuildEvent $event
+   *   The event to process.
+   */
+  public function onRoutingRouteAlterSetAccessCheck(RouteBuildEvent $event) {
+    $this->accessManager->setChecks($event->getRouteCollection());
   }
 
   /**
@@ -43,6 +66,8 @@ public function onKernelRequestAccessCheck(GetResponseEvent $event) {
    */
   static function getSubscribedEvents() {
     $events[KernelEvents::REQUEST][] = array('onKernelRequestAccessCheck', 30);
+    // Setting very low priority to ensure access checks are run after alters.
+    $events[RoutingEvents::ALTER][] = array('onRoutingRouteAlterSetAccessCheck', 0);
 
     return $events;
   }
diff --git a/core/lib/Drupal/Core/EventSubscriber/LegacyAccessSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/LegacyAccessSubscriber.php
new file mode 100644
index 0000000..707de62
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/LegacyAccessSubscriber.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\EventSubscriber\LegacyAccessSubscriber.
+ */
+
+namespace Drupal\Core\EventSubscriber;
+
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Access subscriber for legacy controller requests.
+ */
+class LegacyAccessSubscriber implements EventSubscriberInterface {
+
+  /**
+   * Verifies that the current user can access the requested path.
+   *
+   * @todo This is a total hack to keep our current access system working. It
+   *   should be replaced with something robust and injected at some point.
+   *
+   * @param Symfony\Component\HttpKernel\Event\GetResponseEvent $event
+   *   The Event to process.
+   */
+  public function onKernelRequestAccessCheck(GetResponseEvent $event) {
+
+    $router_item = $event->getRequest()->attributes->get('drupal_menu_item');
+
+    if (isset($router_item['access']) && !$router_item['access']) {
+      throw new AccessDeniedHttpException();
+    }
+  }
+
+  /**
+   * Registers the methods in this class that should be listeners.
+   *
+   * @return array
+   *   An array of event listener definitions.
+   */
+  static function getSubscribedEvents() {
+    $events[KernelEvents::REQUEST][] = array('onKernelRequestAccessCheck', 30);
+
+    return $events;
+  }
+}
diff --git a/core/lib/Drupal/Core/Routing/FirstEntryFinalMatcher.php b/core/lib/Drupal/Core/Routing/FirstEntryFinalMatcher.php
index 45d0888..cc1adde 100644
--- a/core/lib/Drupal/Core/Routing/FirstEntryFinalMatcher.php
+++ b/core/lib/Drupal/Core/Routing/FirstEntryFinalMatcher.php
@@ -53,7 +53,8 @@ public function matchRequest(Request $request) {
 
       preg_match($compiled->getRegex(), $path, $matches);
 
-      return array_merge($this->mergeDefaults($matches, $route->getDefaults()), array('_route' => $name));
+      $route->setOption('_name', $name);
+      return array_merge($this->mergeDefaults($matches, $route->getDefaults()), array('_route' => $route));
     }
   }
 
diff --git a/core/lib/Drupal/Core/Routing/RouteBuildEvent.php b/core/lib/Drupal/Core/Routing/RouteBuildEvent.php
new file mode 100644
index 0000000..017b83a
--- /dev/null
+++ b/core/lib/Drupal/Core/Routing/RouteBuildEvent.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Routing\RouteBuildEvent.
+ */
+
+namespace Drupal\Core\Routing;
+
+use Symfony\Component\EventDispatcher\Event;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * Represents route building information as event.
+ */
+class RouteBuildEvent extends Event {
+
+  /**
+   * The route collection.
+   *
+   * @var \Symfony\Component\Routing\RouteCollection
+   */
+  protected $routeCollection;
+
+  /**
+   * The module name that provides the route.
+   *
+   * @var string
+   */
+  protected $module;
+
+  /**
+   * Constructs a RouteBuildEvent object.
+   */
+  public function __construct(RouteCollection $route_collection, $module) {
+    $this->routeCollection = $route_collection;
+    $this->module = $module;
+  }
+
+  /**
+   * Gets the route collection.
+   */
+  public function getRouteCollection() {
+    return $this->routeCollection;
+  }
+
+  /**
+   * Gets the module that provides the route.
+   */
+  public function getModule() {
+    return $this->module;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Routing/RouteBuilder.php b/core/lib/Drupal/Core/Routing/RouteBuilder.php
index 3a27767..fc12ee8 100644
--- a/core/lib/Drupal/Core/Routing/RouteBuilder.php
+++ b/core/lib/Drupal/Core/Routing/RouteBuilder.php
@@ -7,8 +7,13 @@
 
 namespace Drupal\Core\Routing;
 
-use Drupal\Core\Lock\LockBackendInterface;
 use Symfony\Component\Routing\Matcher\Dumper\MatcherDumperInterface;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\Yaml\Parser;
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Routing\Route;
+
+use Drupal\Core\Lock\LockBackendInterface;
 
 /**
  * Managing class for rebuilding the router table.
@@ -33,16 +38,26 @@ class RouteBuilder {
   protected $lock;
 
   /**
+   * The event dispatcher to notify of routes.
+   *
+   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
+   */
+  protected $dispatcher;
+
+  /**
    * Construcs the RouteBuilder using the passed MatcherDumperInterface.
    *
    * @param \Symfony\Component\Routing\Matcher\Dumper\MatcherDumperInterface $dumper
    *   The matcher dumper used to store the route information.
    * @param \Drupal\Core\Lock\LockBackendInterface $lock
    *   The lock backend.
+   * @param \Symfony\Component\EventDispatcherEventDispatcherInterface
+   *   The event dispatcher to notify of routes.
    */
-  public function __construct(MatcherDumperInterface $dumper, LockBackendInterface $lock) {
+  public function __construct(MatcherDumperInterface $dumper, LockBackendInterface $lock, EventDispatcherInterface $dispatcher) {
     $this->dumper = $dumper;
     $this->lock = $lock;
+    $this->dispatcher = $dispatcher;
   }
 
   /**
@@ -57,15 +72,38 @@ public function rebuild() {
       return;
     }
 
+    $parser = new Parser();
+
     // We need to manually call each module so that we can know which module
     // a given item came from.
-
-    foreach (module_implements('route_info') as $module) {
-      $routes = call_user_func($module . '_route_info');
-      drupal_alter('router_info', $routes, $module);
-      $this->dumper->addRoutes($routes);
+    // @todo Use an injected Extension service rather than module_list():
+    //   http://drupal.org/node/1331486.
+    foreach (module_list() as $module) {
+      $collection = new RouteCollection();
+      $routing_file = DRUPAL_ROOT . '/' . drupal_get_path('module', $module) . '/' . $module . '.routing.yml';
+      if (file_exists($routing_file)) {
+        $routes = $parser->parse(file_get_contents($routing_file));
+        if (!empty($routes)) {
+          foreach ($routes as $name => $route_info) {
+            $defaults = isset($route_info['defaults']) ? $route_info['defaults'] : array();
+            $requirements = isset($route_info['requirements']) ? $route_info['requirements'] : array();
+            $route = new Route($route_info['pattern'], $defaults, $requirements);
+            $collection->add($name, $route);
+          }
+        }
+      }
+      $this->dispatcher->dispatch(RoutingEvents::ALTER, new RouteBuildEvent($collection, $module));
+      $this->dumper->addRoutes($collection);
       $this->dumper->dump(array('route_set' => $module));
     }
+
+    // Now allow modules to register additional, dynamic routes.
+    $collection = new RouteCollection();
+    $this->dispatcher->dispatch(RoutingEvents::DYNAMIC, new RouteBuildEvent($collection, 'dynamic_routes'));
+    $this->dispatcher->dispatch(RoutingEvents::ALTER, new RouteBuildEvent($collection, 'dynamic_routes'));
+    $this->dumper->addRoutes($collection);
+    $this->dumper->dump(array('route_set' => 'dynamic_routes'));
+
     $this->lock->release('router_rebuild');
   }
 
diff --git a/core/lib/Drupal/Core/Routing/RoutingEvents.php b/core/lib/Drupal/Core/Routing/RoutingEvents.php
new file mode 100644
index 0000000..3ca6ef6
--- /dev/null
+++ b/core/lib/Drupal/Core/Routing/RoutingEvents.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Definition of \Drupal\Core\Routing\RoutingEvents.
+ */
+
+namespace Drupal\Core\Routing;
+
+/**
+ * Contains all events thrown in the core routing component.
+ */
+final class RoutingEvents {
+
+  /**
+   * The ALTER event is fired on a route collection to allow changes to routes.
+   *
+   * This event is used to process new routes before they get saved.
+   *
+   * @see \Drupal\Core\Routing\RouteBuildEvent
+   *
+   * @var string
+   */
+  const ALTER = 'routing.route_alter';
+
+  /**
+   * The DYNAMIC event is fired to allow modules to register additional routes.
+   *
+   * Most routes are static, an should be defined as such. Dynamic routes are
+   * only those whose existence changes depending on the state of the system
+   * at runtime, depending on configuration.
+   *
+   * @see \Drupal\Core\Routing\RouteBuildEvent
+   *
+   * @var string
+   */
+  const DYNAMIC = 'routing.route_dynamic';
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/FirstEntryFinalMatcherTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/FirstEntryFinalMatcherTest.php
index a288b9e..c44a492 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Routing/FirstEntryFinalMatcherTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Routing/FirstEntryFinalMatcherTest.php
@@ -61,7 +61,7 @@ public function testFinalMatcherStatic() {
     $matcher->setCollection($collection);
     $attributes = $matcher->matchRequest($request);
 
-    $this->assertEqual($attributes['_route'], 'route_a', 'The correct matching route was found.');
+    $this->assertEqual($attributes['_route']->getOption('_name'), 'route_a', 'The correct matching route was found.');
     $this->assertEqual($attributes['_controller'], 'foo', 'The correct controller was found.');
   }
 
@@ -82,7 +82,7 @@ public function testFinalMatcherPattern() {
     $matcher->setCollection($collection);
     $attributes = $matcher->matchRequest($request);
 
-    $this->assertEqual($attributes['_route'], 'route_a', 'The correct matching route was found.');
+    $this->assertEqual($attributes['_route']->getOption('_name'), 'route_a', 'The correct matching route was found.');
     $this->assertEqual($attributes['_controller'], 'foo', 'The correct controller was found.');
     $this->assertEqual($attributes['value'], 'narf', 'Required placeholder value found.');
   }
@@ -105,7 +105,7 @@ public function testFinalMatcherPatternDefalts() {
     $matcher->setCollection($collection);
     $attributes = $matcher->matchRequest($request);
 
-    $this->assertEqual($attributes['_route'], 'route_a', 'The correct matching route was found.');
+    $this->assertEqual($attributes['_route']->getOption('_name'), 'route_a', 'The correct matching route was found.');
     $this->assertEqual($attributes['_controller'], 'foo', 'The correct controller was found.');
     $this->assertEqual($attributes['value'], 'poink', 'Optional placeholder value used default.');
   }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/HttpMethodMatcherTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/HttpMethodMatcherTest.php
index c98da2e..8055743 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Routing/HttpMethodMatcherTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Routing/HttpMethodMatcherTest.php
@@ -44,7 +44,7 @@ function __construct($test_id = NULL) {
 
     $this->fixtures = new RoutingFixtures();
   }
-  
+
   /**
    * Confirms that the HttpMethod matcher matches properly.
    */
@@ -78,7 +78,7 @@ public function testNestedMatcher() {
 
     $attributes = $matcher->matchRequest($request);
 
-    $this->assertEqual($attributes['_route'], 'route_a', 'The correct matching route was found.');
+    $this->assertEqual($attributes['_route']->getOption('_name'), 'route_a', 'The correct matching route was found.');
   }
 
   /**
diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/NestedMatcherTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/NestedMatcherTest.php
index 444785c..de29538 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Routing/NestedMatcherTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Routing/NestedMatcherTest.php
@@ -60,6 +60,6 @@ public function testNestedMatcher() {
 
     $attributes = $matcher->matchRequest($request);
 
-    $this->assertEqual($attributes['_route'], 'route_a', 'The correct matching route was found.');
+    $this->assertEqual($attributes['_route']->getOption('_name'), 'route_a', 'The correct matching route was found.');
   }
 }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/RouterPermissionTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/RouterPermissionTest.php
new file mode 100644
index 0000000..8964d66
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Routing/RouterPermissionTest.php
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\system\Tests\Routing\RouterTest.
+ */
+
+namespace Drupal\system\Tests\Routing;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Basic tests for access permissions in routes.
+ */
+class RouterPermissionTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('block', 'router_test');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Router Permission tests',
+      'description' => 'Function Tests for the routing permission system.',
+      'group' => 'Routing',
+    );
+  }
+
+  /**
+   * Confirms that the router can get to a controller.
+   */
+  public function testPermissionAccessDenied() {
+
+    $this->drupalGet('router_test/test5');
+    $this->assertResponse(403, 'Access denied for a route where we don\'t have a permission');
+  }
+
+  /**
+   * Confirms that a router path defaults to access denied.
+   *
+   * Unspecified access controls on a route result in an access denied response.
+   */
+  public function testDefaultAccessDenied() {
+
+    $this->drupalGet('router_test/test6');
+    $this->assertResponse(403, 'Access denied by default if no access specified');
+  }
+
+  /**
+   * Confirms that our default controller logic works properly.
+   */
+  public function testPermissionAccessPassed() {
+
+    $user = $this->drupalCreateUser(array('access test5'));
+
+    $this->drupalGet('router_test/test5');
+    $this->assertRaw('test5', 'The correct string was returned because the route was successful.');
+
+  }
+}
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index e4c4ce7..a5a5821 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -566,51 +566,6 @@ function hook_menu_get_item_alter(&$router_item, $path, $original_map) {
 }
 
 /**
- * Defines routes in the new router system.
- *
- * A route is a Symfony Route object.  See the Symfony documentation for more
- * details on the available options.  Of specific note:
- *  - _controller: This is the PHP callable that will handle a request matching
- *              the route.
- *  - _content: This is the PHP callable that will handle the body of a request
- *              matching this route.  A default controller will provide the page
- *              rendering around it.
- *
- * Typically you will only specify one or the other of those properties.
- *
- * @deprecated
- *   This mechanism for registering routes is temporary. It will be replaced
- *   by a more robust mechanism in the near future.  It is documented here
- *   only for completeness.
- */
-function hook_route_info() {
-  $collection = new RouteCollection();
-
-  $route = new Route('router_test/test1', array(
-    '_controller' => '\Drupal\router_test\TestControllers::test1'
-  ));
-  $collection->add('router_test_1', $route);
-
-  $route = new Route('router_test/test2', array(
-    '_content' => '\Drupal\router_test\TestControllers::test2'
-  ));
-  $collection->add('router_test_2', $route);
-
-  $route = new Route('router_test/test3/{value}', array(
-    '_content' => '\Drupal\router_test\TestControllers::test3'
-  ));
-  $collection->add('router_test_3', $route);
-
-  $route = new Route('router_test/test4/{value}', array(
-    '_content' => '\Drupal\router_test\TestControllers::test4',
-    'value' => 'narf',
-  ));
-  $collection->add('router_test_4', $route);
-
-  return $collection;
-}
-
-/**
  * Define menu items and page callbacks.
  *
  * This hook enables modules to register paths in order to define how URL
diff --git a/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/TestControllers.php b/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/TestControllers.php
index fa92fd8..2afb9b3 100644
--- a/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/TestControllers.php
+++ b/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/TestControllers.php
@@ -30,4 +30,12 @@ public function test4($value) {
     return $value;
   }
 
+  public function test5() {
+    return new Response('test5');
+  }
+
+  public function test6() {
+    return new Response('test6');
+  }
+
 }
diff --git a/core/modules/system/tests/modules/router_test/router_test.module b/core/modules/system/tests/modules/router_test/router_test.module
index 4da939d..bbaedd0 100644
--- a/core/modules/system/tests/modules/router_test/router_test.module
+++ b/core/modules/system/tests/modules/router_test/router_test.module
@@ -1,34 +1,13 @@
 <?php
 
-use Symfony\Component\Routing\Route;
-use Symfony\Component\Routing\RouteCollection;
-
 /**
- * Implements hook_router_info().
+ * Implements hook_permission().
  */
-function router_test_route_info() {
-  $collection = new RouteCollection();
-
-  $route = new Route('router_test/test1', array(
-    '_controller' => '\Drupal\router_test\TestControllers::test1'
-  ));
-  $collection->add('router_test_1', $route);
-
-  $route = new Route('router_test/test2', array(
-    '_content' => '\Drupal\router_test\TestControllers::test2'
-  ));
-  $collection->add('router_test_2', $route);
-
-  $route = new Route('router_test/test3/{value}', array(
-    '_content' => '\Drupal\router_test\TestControllers::test3'
-  ));
-  $collection->add('router_test_3', $route);
-
-  $route = new Route('router_test/test4/{value}', array(
-    '_content' => '\Drupal\router_test\TestControllers::test4',
-    'value' => 'narf',
-  ));
-  $collection->add('router_test_4', $route);
-
-  return $collection;
+function router_test_permission() {
+  return array(
+    'access test5' =>  array(
+      'title' => t('Access test5 route'),
+      'description' => t('Test permission only.'),
+    ),
+  );
 }
diff --git a/core/modules/system/tests/modules/router_test/router_test.routing.yml b/core/modules/system/tests/modules/router_test/router_test.routing.yml
new file mode 100644
index 0000000..23971bc
--- /dev/null
+++ b/core/modules/system/tests/modules/router_test/router_test.routing.yml
@@ -0,0 +1,40 @@
+router_test_1:
+  pattern: '/router_test/test1'
+  defaults:
+    _controller: '\Drupal\router_test\TestControllers::test1'
+  requirements:
+    _access: 'TRUE'
+
+router_test_2:
+  pattern: '/router_test/test2'
+  defaults:
+    _content: '\Drupal\router_test\TestControllers::test2'
+  requirements:
+    _access: 'TRUE'
+
+router_test_3:
+  pattern: '/router_test/test3/{value}'
+  defaults:
+    _content: '\Drupal\router_test\TestControllers::test3'
+  requirements:
+    _access: 'TRUE'
+
+router_test_4:
+  pattern: '/router_test/test4/{value}'
+  defaults:
+    _content: '\Drupal\router_test\TestControllers::test4'
+    value: 'narf'
+  requirements:
+    _access: 'TRUE'
+
+router_test_5:
+  pattern: 'router_test/test5'
+  defaults:
+    _controller: '\Drupal\router_test\TestControllers::test5'
+  requirements:
+    _permission: 'access test5'
+
+router_test_6:
+  pattern: 'router_test/test6'
+  defaults:
+    _controller: '\Drupal\router_test\TestControllers::test6'
diff --git a/core/update.php b/core/update.php
index 968e8f4..98296bb 100644
--- a/core/update.php
+++ b/core/update.php
@@ -459,7 +459,8 @@ function update_check_requirements($skip_warnings = FALSE) {
   ->addArgument(new Reference('database'));
 $container->register('router.builder', 'Drupal\Core\Routing\RouteBuilder')
   ->addArgument(new Reference('router.dumper'))
-  ->addArgument(new Reference('lock'));
+  ->addArgument(new Reference('lock'))
+  ->addArgument(new Reference('dispatcher'));
 
 // Turn error reporting back on. From now on, only fatal errors (which are
 // not passed through the error handler) will cause a message to be printed.
