diff --git a/core/lib/Drupal/Core/Entity/Controller/EntityController.php b/core/lib/Drupal/Core/Entity/Controller/EntityController.php
index ebee7cc..4e4b88b 100644
--- a/core/lib/Drupal/Core/Entity/Controller/EntityController.php
+++ b/core/lib/Drupal/Core/Entity/Controller/EntityController.php
@@ -229,6 +229,10 @@ public function title(RouteMatchInterface $route_match, EntityInterface $_entity
     }
   }
 
+  public function configController() {
+    return [];
+  }
+
   /**
    * Provides a generic edit title callback.
    *
diff --git a/core/lib/Drupal/Core/Entity/Routing/EmptyRouteProvider.php b/core/lib/Drupal/Core/Entity/Routing/EmptyRouteProvider.php
new file mode 100644
index 0000000..f80c775
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Routing/EmptyRouteProvider.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\Routing\EmptyRouteProvider.
+ */
+
+namespace Drupal\Core\Entity\Routing;
+
+use Drupal\Core\Entity\EntityTypeInterface;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+class EmptyRouteProvider implements EntityRouteProviderInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRoutes(EntityTypeInterface $entity_type) {
+    $routes = new RouteCollection();
+
+    if ($link_template = $entity_type->getLinkTemplate('canonical')) {
+      $entity_type_id = $entity_type->id();
+      $route = new Route($link_template);
+      $route
+        ->setDefault('_controller', '\Drupal\Core\Entity\Controller\EntityController::configController')
+        ->setRequirement('_entity_access', "{$entity_type_id}.view")
+        ->setOption('parameters', [
+          $entity_type_id => ['type' => 'entity:' . $entity_type_id],
+        ]);
+      $routes->add("entity.{$entity_type_id}.canonical", $route);
+    }
+    return $routes;
+  }
+
+}
diff --git a/core/modules/config/tests/config_test/src/Entity/ConfigTest.php b/core/modules/config/tests/config_test/src/Entity/ConfigTest.php
index 56ddfe6..14e70cd 100644
--- a/core/modules/config/tests/config_test/src/Entity/ConfigTest.php
+++ b/core/modules/config/tests/config_test/src/Entity/ConfigTest.php
@@ -20,7 +20,10 @@
  *       "default" = "Drupal\config_test\ConfigTestForm",
  *       "delete" = "Drupal\Core\Entity\EntityDeleteForm"
  *     },
- *     "access" = "Drupal\config_test\ConfigTestAccessControlHandler"
+ *     "access" = "Drupal\config_test\ConfigTestAccessControlHandler",
+ *     "route_provider" = {
+ *       "default" = "\Drupal\Core\Entity\Routing\EmptyRouteProvider",
+ *     },
  *   },
  *   config_prefix = "dynamic",
  *   entity_keys = {
@@ -29,6 +32,7 @@
  *     "status" = "status"
  *   },
  *   links = {
+ *     "canonical" = "/config_test/{config_test}",
  *     "edit-form" = "/admin/structure/config_test/manage/{config_test}",
  *     "delete-form" = "/admin/structure/config_test/manage/{config_test}/delete",
  *     "enable" = "/admin/structure/config_test/manage/{config_test}/enable",
diff --git a/core/modules/rest/src/Plugin/Deriver/EntityDeriver.php b/core/modules/rest/src/Plugin/Deriver/EntityDeriver.php
index 6a6ddae..b7c6e5f 100644
--- a/core/modules/rest/src/Plugin/Deriver/EntityDeriver.php
+++ b/core/modules/rest/src/Plugin/Deriver/EntityDeriver.php
@@ -69,6 +69,9 @@ public function getDerivativeDefinitions($base_plugin_definition) {
           'id' => 'entity:' . $entity_type_id,
           'entity_type' => $entity_type_id,
           'serialization_class' => $entity_type->getClass(),
+          'serialization_context' => [
+            'entity_type' => $entity_type_id,
+          ],
           'label' => $entity_type->getLabel(),
         );
 
diff --git a/core/modules/rest/src/Plugin/Type/ResourcePluginManager.php b/core/modules/rest/src/Plugin/Type/ResourcePluginManager.php
index 4aa46b2..fde0333 100644
--- a/core/modules/rest/src/Plugin/Type/ResourcePluginManager.php
+++ b/core/modules/rest/src/Plugin/Type/ResourcePluginManager.php
@@ -17,6 +17,13 @@
 class ResourcePluginManager extends DefaultPluginManager {
 
   /**
+   * {@inheritdoc}
+   */
+  protected $defaults = [
+    'serialization_context' => [],
+  ];
+
+  /**
    * Constructs a new \Drupal\rest\Plugin\Type\ResourcePluginManager object.
    *
    * @param \Traversable $namespaces
diff --git a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
index 6ec5f26..9c9f8ee 100644
--- a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
+++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
@@ -2,8 +2,10 @@
 
 namespace Drupal\rest\Plugin\rest\resource;
 
+use Drupal\Core\Entity\FieldableEntityInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityStorageException;
+use Drupal\Core\Url;
 use Drupal\rest\Plugin\ResourceBase;
 use Drupal\rest\ResourceResponse;
 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
@@ -48,13 +50,16 @@ public function get(EntityInterface $entity) {
     $response = new ResourceResponse($entity, 200);
     $response->addCacheableDependency($entity);
     $response->addCacheableDependency($entity_access);
-    foreach ($entity as $field_name => $field) {
-      /** @var \Drupal\Core\Field\FieldItemListInterface $field */
-      $field_access = $field->access('view', NULL, TRUE);
-      $response->addCacheableDependency($field_access);
 
-      if (!$field_access->isAllowed()) {
-        $entity->set($field_name, NULL);
+    if ($entity instanceof FieldableEntityInterface) {
+      foreach ($entity as $field_name => $field) {
+        /** @var \Drupal\Core\Field\FieldItemListInterface $field */
+        $field_access = $field->access('view', NULL, TRUE);
+        $response->addCacheableDependency($field_access);
+
+        if (!$field_access->isAllowed()) {
+          $entity->set($field_name, NULL);
+        }
       }
     }
 
@@ -95,9 +100,11 @@ public function post(EntityInterface $entity = NULL) {
     // Only check 'edit' permissions for fields that were actually
     // submitted by the user. Field access makes no difference between 'create'
     // and 'update', so the 'edit' operation is used here.
-    foreach ($entity->_restSubmittedFields as $key => $field_name) {
-      if (!$entity->get($field_name)->access('edit')) {
-        throw new AccessDeniedHttpException("Access denied on creating field '$field_name'");
+    if ($entity instanceof FieldableEntityInterface) {
+      foreach ($entity->_restSubmittedFields as $key => $field_name) {
+        if (!$entity->get($field_name)->access('edit')) {
+          throw new AccessDeniedHttpException("Access denied on creating field '$field_name'");
+        }
       }
     }
 
@@ -109,7 +116,13 @@ public function post(EntityInterface $entity = NULL) {
 
       // 201 Created responses return the newly created entity in the response
       // body.
-      $url = $entity->urlInfo('canonical', ['absolute' => TRUE])->toString(TRUE);
+      if ($entity->getEntityType()->hasLinkTemplate('canonical')) {
+        $url = $entity->urlInfo('canonical', ['absolute' => TRUE])->toString(TRUE);
+      }
+      else {
+        $url = Url::fromRoute('<front>')->setAbsolute()->toString(TRUE);
+      }
+
       $response = new ResourceResponse($entity, 201, ['Location' => $url->getGeneratedUrl()]);
       // Responses after creating an entity are not cacheable, so we add no
       // cacheability metadata here.
@@ -212,6 +225,9 @@ public function delete(EntityInterface $entity) {
    *   If validation errors are found.
    */
   protected function validate(EntityInterface $entity) {
+    if (!$entity instanceof FieldableEntityInterface) {
+      return;
+    }
     $violations = $entity->validate();
 
     // Remove violations of inaccessible fields as they cannot stem from our
diff --git a/core/modules/rest/src/RequestHandler.php b/core/modules/rest/src/RequestHandler.php
index 8e0cd74..23956d0 100644
--- a/core/modules/rest/src/RequestHandler.php
+++ b/core/modules/rest/src/RequestHandler.php
@@ -41,6 +41,7 @@ public function handle(RouteMatchInterface $route_match, Request $request) {
       ->createInstance($plugin);
 
     // Deserialize incoming data if available.
+    /** @var \Symfony\Component\Serializer\SerializerInterface $serializer */
     $serializer = $this->container->get('serializer');
     $received = $request->getContent();
     $unserialized = NULL;
@@ -57,7 +58,7 @@ public function handle(RouteMatchInterface $route_match, Request $request) {
         $definition = $resource->getPluginDefinition();
         $class = $definition['serialization_class'];
         try {
-          $unserialized = $serializer->deserialize($received, $class, $format, array('request_method' => $method));
+          $unserialized = $serializer->deserialize($received, $class, $format, array('request_method' => $method) + $definition['serialization_context']);
         }
         catch (UnexpectedValueException $e) {
           $error['error'] = $e->getMessage();
diff --git a/core/modules/rest/src/Tests/RESTTestBase.php b/core/modules/rest/src/Tests/RESTTestBase.php
index 71b1a08..31a1dad 100644
--- a/core/modules/rest/src/Tests/RESTTestBase.php
+++ b/core/modules/rest/src/Tests/RESTTestBase.php
@@ -217,6 +217,11 @@ protected function entityValues($entity_type) {
             'format' => 'plain_text',
           )),
         );
+      case 'config_test':
+        return [
+          'id' => $this->randomMachineName(),
+          'label' => 'Test label',
+        ];
       case 'node':
         return array('title' => $this->randomString(), 'type' => 'resttest');
       case 'node_type':
diff --git a/core/modules/rest/src/Tests/ReadTest.php b/core/modules/rest/src/Tests/ReadTest.php
index 31b5db8..30f1644 100644
--- a/core/modules/rest/src/Tests/ReadTest.php
+++ b/core/modules/rest/src/Tests/ReadTest.php
@@ -3,6 +3,7 @@
 namespace Drupal\rest\Tests;
 
 use Drupal\Component\Serialization\Json;
+use Drupal\Core\Config\Entity\ConfigEntityInterface;
 use Drupal\Core\Url;
 
 /**
@@ -17,7 +18,7 @@ class ReadTest extends RESTTestBase {
    *
    * @var array
    */
-  public static $modules = array('hal', 'rest', 'entity_test');
+  public static $modules = array('hal', 'rest', 'entity_test', 'config_test');
 
   /**
    * Tests several valid and invalid read requests on all entity types.
@@ -25,7 +26,7 @@ class ReadTest extends RESTTestBase {
   public function testRead() {
     // @todo Expand this at least to users.
     // Define the entity types we want to test.
-    $entity_types = array('entity_test', 'node');
+    $entity_types = array('entity_test', 'node', 'config_test');
     foreach ($entity_types as $entity_type) {
       $this->enableService('entity:' . $entity_type, 'GET');
       // Create a user account that has the required permissions to read
@@ -39,23 +40,41 @@ public function testRead() {
       $entity = $this->entityCreate($entity_type);
       $entity->save();
       // Read it over the REST API.
-      $response = $this->httpRequest($entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET');
+      $response = $this->httpRequest($entity->urlInfo('canonical')->setRouteParameter('_format', $this->defaultFormat), 'GET');
       $this->assertResponse('200', 'HTTP response code is correct.');
       $this->assertHeader('content-type', $this->defaultMimeType);
       $data = Json::decode($response);
       // Only assert one example property here, other properties should be
       // checked in serialization tests.
-      $this->assertEqual($data['uuid'][0]['value'], $entity->uuid(), 'Entity UUID is correct');
+
+      if ($entity instanceof ConfigEntityInterface) {
+        $this->assertEqual($data['uuid'], $entity->uuid(), 'Entity UUID is correct');
+      }
+      else {
+        $this->assertEqual($data['uuid'][0]['value'], $entity->uuid(), 'Entity UUID is correct');
+      }
 
       // Try to read the entity with an unsupported mime format.
-      $response = $this->httpRequest($entity->urlInfo()->setRouteParameter('_format', 'wrongformat'), 'GET');
+      $response = $this->httpRequest($entity->urlInfo('canonical')->setRouteParameter('_format', 'wrongformat'), 'GET');
       $this->assertResponse(406);
       $this->assertHeader('Content-type', 'application/json');
 
       // Try to read an entity that does not exist.
       $response = $this->httpRequest(Url::fromUri('base://' . $entity_type . '/9999', ['query' => ['_format' => $this->defaultFormat]]), 'GET');
       $this->assertResponse(404);
-      $path = $entity_type == 'node' ? '/node/{node}' : '/entity_test/{entity_test}';
+      switch ($entity_type) {
+        case 'node':
+          $path = '/node/{node}';
+          break;
+        case 'entity_test':
+          $path = '/entity_test/{entity_test}';
+          break;
+        case 'config_test':
+          $path = '/config_test/{config_test}';
+          break;
+        default:
+          throw new \RuntimeException('This entity type is not supported');
+      }
       $expected_message = Json::encode(['message' => 'The "' . $entity_type . '" parameter was not converted for the path "' . $path . '" (route name: "rest.entity.' . $entity_type . '.GET.hal_json")']);
       $this->assertIdentical($expected_message, $response, 'Response message is correct.');
 
@@ -65,7 +84,7 @@ public function testRead() {
       if ($entity_type == 'entity_test') {
         $entity->field_test_text->value = 'no access value';
         $entity->save();
-        $response = $this->httpRequest($entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET');
+        $response = $this->httpRequest($entity->urlInfo('canonical')->setRouteParameter('_format', $this->defaultFormat), 'GET');
         $this->assertResponse(200);
         $this->assertHeader('content-type', $this->defaultMimeType);
         $data = Json::decode($response);
@@ -74,14 +93,14 @@ public function testRead() {
 
       // Try to read an entity without proper permissions.
       $this->drupalLogout();
-      $response = $this->httpRequest($entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET');
+      $response = $this->httpRequest($entity->urlInfo('canonical')->setRouteParameter('_format', $this->defaultFormat), 'GET');
       $this->assertResponse(403);
       $this->assertIdentical('{"message":""}', $response);
     }
     // Try to read a resource which is not REST API enabled.
     $account = $this->drupalCreateUser();
     $this->drupalLogin($account);
-    $response = $this->httpRequest($account->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET');
+    $response = $this->httpRequest($account->urlInfo('canonical')->setRouteParameter('_format', $this->defaultFormat), 'GET');
     // \Drupal\Core\Routing\RequestFormatRouteFilter considers the canonical,
     // non-REST route a match, but a lower quality one: no format restrictions
     // means there's always a match and hence when there is no matching REST
