 .../Drupal/Core/Entity/EntityCreateAccessCheck.php |  2 +-
 .../EventSubscriber/AuthenticationSubscriber.php   | 16 ++++++
 .../RouteAccessResponseSubscriber.php              |  2 +-
 .../block/src/BlockAccessControlHandler.php        |  5 +-
 .../comment/src/CommentAccessControlHandler.php    |  9 +++-
 .../media/src/MediaAccessControlHandler.php        | 13 ++++-
 .../src/MenuLinkContentAccessControlHandler.php    |  4 +-
 .../src/Plugin/rest/resource/EntityResource.php    | 60 ++++++++--------------
 core/modules/rest/src/Routing/ResourceRoutes.php   |  4 +-
 .../config_test_rest/config_test_rest.module       |  7 ++-
 .../EntityResource/Block/BlockResourceTestBase.php |  2 +-
 .../Comment/CommentResourceTestBase.php            |  6 ++-
 .../ConfigTest/ConfigTestResourceTestBase.php      | 16 ++++++
 .../EntityResource/EntityResourceTestBase.php      | 48 ++++++++---------
 .../EntityResource/Media/MediaResourceTestBase.php |  4 +-
 .../MenuLinkContentResourceTestBase.php            |  2 +-
 .../ShortcutSet/ShortcutSetResourceTestBase.php    | 16 ++++++
 .../EntityResource/User/UserResourceTestBase.php   |  4 +-
 .../src/ShortcutSetAccessControlHandler.php        |  2 +-
 core/modules/user/src/UserAccessControlHandler.php | 10 +++-
 20 files changed, 149 insertions(+), 83 deletions(-)

diff --git a/core/lib/Drupal/Core/Entity/EntityCreateAccessCheck.php b/core/lib/Drupal/Core/Entity/EntityCreateAccessCheck.php
index 9ceac52..b3c12a8 100644
--- a/core/lib/Drupal/Core/Entity/EntityCreateAccessCheck.php
+++ b/core/lib/Drupal/Core/Entity/EntityCreateAccessCheck.php
@@ -62,7 +62,7 @@ public function access(Route $route, RouteMatchInterface $route_match, AccountIn
       }
       // If we were unable to replace all placeholders, deny access.
       if (strpos($bundle, '{') !== FALSE) {
-        return AccessResult::neutral();
+        return AccessResult::neutral(sprintf("Could not find '%s' request argument, therefore cannot check create access.", $bundle));
       }
     }
     return $this->entityManager->getAccessControlHandler($entity_type)->createAccess($bundle, $account, [], TRUE);
diff --git a/core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php
index be3f55e..df5de13 100644
--- a/core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php
@@ -125,6 +125,21 @@ public function onExceptionSendChallenge(GetResponseForExceptionEvent $event) {
   }
 
   /**
+   * Detect disallowed authentication methods on access denied exceptions.
+   *
+   * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
+   */
+  public function onExceptionAccessDenied(GetResponseForExceptionEvent $event) {
+    if ($event->getRequestType() === HttpKernelInterface::MASTER_REQUEST) {
+      $request = $event->getRequest();
+      $exception = $event->getException();
+      if ($exception instanceof AccessDeniedHttpException && $this->authenticationProvider->applies($request) && !$this->filter->appliesToRoutedRequest($request, TRUE)) {
+        $event->setException(new AccessDeniedHttpException('The used authentication method is not allowed on this route.'));
+      }
+    }
+  }
+
+  /**
    * {@inheritdoc}
    */
   public static function getSubscribedEvents() {
@@ -137,6 +152,7 @@ public static function getSubscribedEvents() {
     // Access check must be performed after routing.
     $events[KernelEvents::REQUEST][] = ['onKernelRequestFilterProvider', 31];
     $events[KernelEvents::EXCEPTION][] = ['onExceptionSendChallenge', 75];
+    $events[KernelEvents::EXCEPTION][] = ['onExceptionAccessDenied', 75];
     return $events;
   }
 
diff --git a/core/lib/Drupal/Core/EventSubscriber/RouteAccessResponseSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/RouteAccessResponseSubscriber.php
index b2f1312..83d277f 100644
--- a/core/lib/Drupal/Core/EventSubscriber/RouteAccessResponseSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/RouteAccessResponseSubscriber.php
@@ -50,7 +50,7 @@ public function onRespond(FilterResponseEvent $event) {
   public static function getSubscribedEvents() {
     // Priority 10, so that it runs before FinishResponseSubscriber, which will
     // expose the cacheability metadata in the form of headers.
-    $events[KernelEvents::RESPONSE][] = ['onRespond', 10];
+    $events[KernelEvents::RESPONSE][] = ['onRespond', 110];
     return $events;
   }
 
diff --git a/core/modules/block/src/BlockAccessControlHandler.php b/core/modules/block/src/BlockAccessControlHandler.php
index 35af61e..0ce8384 100644
--- a/core/modules/block/src/BlockAccessControlHandler.php
+++ b/core/modules/block/src/BlockAccessControlHandler.php
@@ -128,7 +128,10 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter
         }
       }
       else {
-        $access = AccessResult::forbidden();
+        $reason = count($conditions) > 1
+          ? "One of the block visibility conditions ('%s') denied access."
+          : "The block visibility condition '%s' denied access.";
+        $access = AccessResult::forbidden(sprintf($reason, implode("', '", array_keys($conditions))));
       }
 
       $this->mergeCacheabilityFromConditions($access, $conditions);
diff --git a/core/modules/comment/src/CommentAccessControlHandler.php b/core/modules/comment/src/CommentAccessControlHandler.php
index bcb0fd7..1c152d3 100644
--- a/core/modules/comment/src/CommentAccessControlHandler.php
+++ b/core/modules/comment/src/CommentAccessControlHandler.php
@@ -3,6 +3,7 @@
 namespace Drupal\comment;
 
 use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Access\AccessResultReasonInterface;
 use Drupal\Core\Entity\EntityAccessControlHandler;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Field\FieldDefinitionInterface;
@@ -45,11 +46,15 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter
         return $access_result;
 
       case 'update':
-        return AccessResult::allowedIf($account->id() && $account->id() == $entity->getOwnerId() && $entity->isPublished() && $account->hasPermission('edit own comments'))->cachePerPermissions()->cachePerUser()->addCacheableDependency($entity);
+        $access_result = AccessResult::allowedIf($account->id() && $account->id() == $entity->getOwnerId() && $entity->isPublished() && $account->hasPermission('edit own comments'))->cachePerPermissions()->cachePerUser()->addCacheableDependency($entity);
+        if (!$access_result->isAllowed() && $access_result instanceof AccessResultReasonInterface) {
+          $access_result->setReason("The 'edit own comments' permission is required and the comment must be published.");
+        }
+        return $access_result;
 
       default:
         // No opinion.
-        return AccessResult::neutral()->cachePerPermissions();
+        return AccessResult::neutral("The 'administer comments' permission is required.")->cachePerPermissions();
     }
   }
 
diff --git a/core/modules/media/src/MediaAccessControlHandler.php b/core/modules/media/src/MediaAccessControlHandler.php
index 434abfe..a8779e7 100644
--- a/core/modules/media/src/MediaAccessControlHandler.php
+++ b/core/modules/media/src/MediaAccessControlHandler.php
@@ -3,6 +3,7 @@
 namespace Drupal\media;
 
 use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Access\AccessResultReasonInterface;
 use Drupal\Core\Entity\EntityAccessControlHandler;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Session\AccountInterface;
@@ -35,19 +36,27 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter
         if ($account->hasPermission('update any media')) {
           return AccessResult::allowed()->cachePerPermissions();
         }
-        return AccessResult::allowedIf($account->hasPermission('update media') && $is_owner)
+        $access_result = AccessResult::allowedIf($account->hasPermission('update media') && $is_owner)
           ->cachePerPermissions()
           ->cachePerUser()
           ->addCacheableDependency($entity);
+        if (!$access_result->isAllowed() && $access_result instanceof AccessResultReasonInterface) {
+          $access_result->setReason("As a non-owner of this media item, the 'update any media' permission is required; as an owner of this media, the 'update media' permission is required.");
+        }
+        return $access_result;
 
       case 'delete':
         if ($account->hasPermission('delete any media')) {
           return AccessResult::allowed()->cachePerPermissions();
         }
-        return AccessResult::allowedIf($account->hasPermission('delete media') && $is_owner)
+        $access_result = AccessResult::allowedIf($account->hasPermission('delete media') && $is_owner)
           ->cachePerPermissions()
           ->cachePerUser()
           ->addCacheableDependency($entity);
+        if (!$access_result->isAllowed() && $access_result instanceof AccessResultReasonInterface) {
+          $access_result->setReason("As a non-owner of this media item, the 'delete any media' permission is required; as an owner of this media, the 'delete media' permission is required.");
+        }
+        return $access_result;
 
       default:
         return AccessResult::neutral()->cachePerPermissions();
diff --git a/core/modules/menu_link_content/src/MenuLinkContentAccessControlHandler.php b/core/modules/menu_link_content/src/MenuLinkContentAccessControlHandler.php
index eadf045..9e6d814 100644
--- a/core/modules/menu_link_content/src/MenuLinkContentAccessControlHandler.php
+++ b/core/modules/menu_link_content/src/MenuLinkContentAccessControlHandler.php
@@ -4,6 +4,7 @@
 
 use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Access\AccessManagerInterface;
+use Drupal\Core\Access\AccessResultReasonInterface;
 use Drupal\Core\Entity\EntityAccessControlHandler;
 use Drupal\Core\Entity\EntityHandlerInterface;
 use Drupal\Core\Entity\EntityInterface;
@@ -72,7 +73,8 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter
         }
 
       case 'delete':
-        return AccessResult::allowedIf(!$entity->isNew() && $account->hasPermission('administer menu'))->cachePerPermissions()->addCacheableDependency($entity);
+        return AccessResult::allowedIfHasPermission($account, 'administer menu')
+          ->andIf(AccessResult::allowedIf(!$entity->isNew())->addCacheableDependency($entity));
     }
   }
 
diff --git a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
index 5d9849d..be77872 100644
--- a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
+++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
@@ -120,14 +120,8 @@ public static function create(ContainerInterface $container, array $configuratio
    * @throws \Symfony\Component\HttpKernel\Exception\HttpException
    */
   public function get(EntityInterface $entity) {
-    $entity_access = $entity->access('view', NULL, TRUE);
-    if (!$entity_access->isAllowed()) {
-      throw new AccessDeniedHttpException($entity_access->getReason() ?: $this->generateFallbackAccessDeniedMessage($entity, 'view'));
-    }
-
     $response = new ResourceResponse($entity, 200);
     $response->addCacheableDependency($entity);
-    $response->addCacheableDependency($entity_access);
 
     if ($entity instanceof FieldableEntityInterface) {
       foreach ($entity as $field_name => $field) {
@@ -162,10 +156,6 @@ public function post(EntityInterface $entity = NULL) {
       throw new BadRequestHttpException('No entity content received.');
     }
 
-    $entity_access = $entity->access('create', NULL, TRUE);
-    if (!$entity_access->isAllowed()) {
-      throw new AccessDeniedHttpException($entity_access->getReason() ?: $this->generateFallbackAccessDeniedMessage($entity, 'create'));
-    }
     $definition = $this->getPluginDefinition();
     // Verify that the deserialized entity is of the type that we expect to
     // prevent security issues.
@@ -257,10 +247,6 @@ public function patch(EntityInterface $original_entity, EntityInterface $entity
     if ($entity->getEntityTypeId() != $definition['entity_type']) {
       throw new BadRequestHttpException('Invalid entity type');
     }
-    $entity_access = $original_entity->access('update', NULL, TRUE);
-    if (!$entity_access->isAllowed()) {
-      throw new AccessDeniedHttpException($entity_access->getReason() ?: $this->generateFallbackAccessDeniedMessage($entity, 'update'));
-    }
 
     // Overwrite the received properties.
     $entity_keys = $entity->getEntityType()->getKeys();
@@ -322,10 +308,6 @@ public function patch(EntityInterface $original_entity, EntityInterface $entity
    * @throws \Symfony\Component\HttpKernel\Exception\HttpException
    */
   public function delete(EntityInterface $entity) {
-    $entity_access = $entity->access('delete', NULL, TRUE);
-    if (!$entity_access->isAllowed()) {
-      throw new AccessDeniedHttpException($entity_access->getReason() ?: $this->generateFallbackAccessDeniedMessage($entity, 'delete'));
-    }
     try {
       $entity->delete();
       $this->logger->notice('Deleted entity %type with ID %id.', ['%type' => $entity->getEntityTypeId(), '%id' => $entity->id()]);
@@ -339,26 +321,6 @@ public function delete(EntityInterface $entity) {
   }
 
   /**
-   * Generates a fallback access denied message, when no specific reason is set.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity object.
-   * @param string $operation
-   *   The disallowed entity operation.
-   *
-   * @return string
-   *   The proper message to display in the AccessDeniedHttpException.
-   */
-  protected function generateFallbackAccessDeniedMessage(EntityInterface $entity, $operation) {
-    $message = "You are not authorized to {$operation} this {$entity->getEntityTypeId()} entity";
-
-    if ($entity->bundle() !== $entity->getEntityTypeId()) {
-      $message .= " of bundle {$entity->bundle()}";
-    }
-    return "{$message}.";
-  }
-
-  /**
    * {@inheritdoc}
    */
   public function permissions() {
@@ -378,6 +340,28 @@ public function permissions() {
    */
   protected function getBaseRoute($canonical_path, $method) {
     $route = parent::getBaseRoute($canonical_path, $method);
+
+    switch ($method) {
+      case 'GET':
+        $route->setRequirement('_entity_access', $this->entityType->id() . '.view');
+        break;
+      case 'POST':
+        if ($this->entityType->getBundleEntityType()) {
+          $route->setRequirement('_entity_create_access', $this->entityType->id() . ':{' . $this->entityType->getBundleEntityType() . '}');
+        }
+        else {
+          $route->setRequirement('_entity_create_access', $this->entityType->id());
+        }
+        break;
+      case 'PATCH':
+        $route->setRequirement('_entity_access', $this->entityType->id() . '.update');
+        break;
+      case 'DELETE':
+        $route->setRequirement('_entity_access', $this->entityType->id() . '.delete');
+        break;
+    }
+
+    if ($method)
     $definition = $this->getPluginDefinition();
 
     $parameters = $route->getOption('parameters') ?: [];
diff --git a/core/modules/rest/src/Routing/ResourceRoutes.php b/core/modules/rest/src/Routing/ResourceRoutes.php
index 5ba4c5d..5012dee 100644
--- a/core/modules/rest/src/Routing/ResourceRoutes.php
+++ b/core/modules/rest/src/Routing/ResourceRoutes.php
@@ -94,7 +94,9 @@ protected function getRoutesForResourceConfig(RestResourceConfigInterface $rest_
       $methods = $route->getMethods();
       // Only expose routes where the method is enabled in the configuration.
       if ($methods && ($method = $methods[0]) && $supported_formats = $rest_resource_config->getFormats($method)) {
-        $route->setRequirement('_csrf_request_header_token', 'TRUE');
+        if (!in_array($method, ['GET', 'HEAD'], TRUE)) {
+          $route->setRequirement('_csrf_request_header_token', 'TRUE');
+        }
 
         // Check that authentication providers are defined.
         if (empty($rest_resource_config->getAuthenticationProviders($method))) {
diff --git a/core/modules/rest/tests/modules/config_test_rest/config_test_rest.module b/core/modules/rest/tests/modules/config_test_rest/config_test_rest.module
index fcd9979..aa9faba 100644
--- a/core/modules/rest/tests/modules/config_test_rest/config_test_rest.module
+++ b/core/modules/rest/tests/modules/config_test_rest/config_test_rest.module
@@ -6,6 +6,7 @@
  */
 
 use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Access\AccessResultReasonInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Session\AccountInterface;
 
@@ -26,5 +27,9 @@ function config_test_rest_config_test_access(EntityInterface $entity, $operation
   // Add permission, so that EntityResourceTestBase's scenarios can test access
   // being denied. By default, all access is always allowed for the config_test
   // config entity.
-  return AccessResult::forbiddenIf(!$account->hasPermission('view config_test'))->cachePerPermissions();
+  $access_result = AccessResult::forbiddenIf(!$account->hasPermission('view config_test'))->cachePerPermissions();
+  if (!$access_result->isAllowed() && $access_result instanceof AccessResultReasonInterface) {
+    $access_result->setReason("The 'view config_test' permission is required.");
+  }
+  return $access_result;
 }
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Block/BlockResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Block/BlockResourceTestBase.php
index d86f9b1..2045e7f 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/Block/BlockResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/Block/BlockResourceTestBase.php
@@ -135,7 +135,7 @@ protected function getExpectedUnauthorizedAccessMessage($method) {
 
     switch ($method) {
       case 'GET':
-        return "You are not authorized to view this block entity.";
+        return "The block visibility condition 'user_role' denied access.";
       default:
         return parent::getExpectedUnauthorizedAccessMessage($method);
     }
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentResourceTestBase.php
index bade2a7..b22b9dd 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/Comment/CommentResourceTestBase.php
@@ -317,10 +317,12 @@ protected function getExpectedUnauthorizedAccessMessage($method) {
     switch ($method) {
       case 'GET';
         return "The 'access comments' permission is required and the comment must be published.";
+      case 'PATCH':
+        return "The 'edit own comments' permission is required and the comment must be published.";
       case 'POST';
         return "The 'post comments' permission is required.";
-      default:
-        return parent::getExpectedUnauthorizedAccessMessage($method);
+      case 'DELETE':
+        return "The 'administer comments' permission is required.";
     }
   }
 
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/ConfigTest/ConfigTestResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/ConfigTest/ConfigTestResourceTestBase.php
index 9fe073b..e3e3099 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/ConfigTest/ConfigTestResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/ConfigTest/ConfigTestResourceTestBase.php
@@ -70,4 +70,20 @@ protected function getNormalizedPostEntity() {
     // @todo Update in https://www.drupal.org/node/2300677.
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  protected function getExpectedUnauthorizedAccessMessage($method) {
+    if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) {
+      return parent::getExpectedUnauthorizedAccessMessage($method);
+    }
+
+    switch ($method) {
+      case 'GET':
+        return "The 'view config_test' permission is required.";
+      default:
+        return parent::getExpectedUnauthorizedAccessMessage($method);
+    }
+  }
+
 }
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
index f7e783b..329c993 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
@@ -771,18 +771,6 @@ public function testPost() {
 
     $request_options[RequestOptions::HEADERS]['Content-Type'] = static::$mimeType;
 
-    // DX: 400 when no request body.
-    $response = $this->request('POST', $url, $request_options);
-    $this->assertResourceErrorResponse(400, 'No entity content received.', $response);
-
-    $request_options[RequestOptions::BODY] = $unparseable_request_body;
-
-    // DX: 400 when unparseable request body.
-    $response = $this->request('POST', $url, $request_options);
-    $this->assertResourceErrorResponse(400, 'Syntax error', $response);
-
-    $request_options[RequestOptions::BODY] = $parseable_invalid_request_body;
-
     if (static::$auth) {
       // DX: forgetting authentication: authentication provider-specific error
       // response.
@@ -798,6 +786,18 @@ public function testPost() {
 
     $this->setUpAuthorization('POST');
 
+    // DX: 400 when no request body.
+    $response = $this->request('POST', $url, $request_options);
+    $this->assertResourceErrorResponse(400, 'No entity content received.', $response);
+
+    $request_options[RequestOptions::BODY] = $unparseable_request_body;
+
+    // DX: 400 when unparseable request body.
+    $response = $this->request('POST', $url, $request_options);
+    $this->assertResourceErrorResponse(400, 'Syntax error', $response);
+
+    $request_options[RequestOptions::BODY] = $parseable_invalid_request_body;
+
     // DX: 422 when invalid entity: multiple values sent for single-value field.
     $response = $this->request('POST', $url, $request_options);
     $label_field = $this->entity->getEntityType()->hasKey('label') ? $this->entity->getEntityType()->getKey('label') : static::$labelFieldName;
@@ -979,18 +979,6 @@ public function testPatch() {
 
     $request_options[RequestOptions::HEADERS]['Content-Type'] = static::$mimeType;
 
-    // DX: 400 when no request body.
-    $response = $this->request('PATCH', $url, $request_options);
-    $this->assertResourceErrorResponse(400, 'No entity content received.', $response);
-
-    $request_options[RequestOptions::BODY] = $unparseable_request_body;
-
-    // DX: 400 when unparseable request body.
-    $response = $this->request('PATCH', $url, $request_options);
-    $this->assertResourceErrorResponse(400, 'Syntax error', $response);
-
-    $request_options[RequestOptions::BODY] = $parseable_invalid_request_body;
-
     if (static::$auth) {
       // DX: forgetting authentication: authentication provider-specific error
       // response.
@@ -1006,6 +994,18 @@ public function testPatch() {
 
     $this->setUpAuthorization('PATCH');
 
+    // DX: 400 when no request body.
+    $response = $this->request('PATCH', $url, $request_options);
+    $this->assertResourceErrorResponse(400, 'No entity content received.', $response);
+
+    $request_options[RequestOptions::BODY] = $unparseable_request_body;
+
+    // DX: 400 when unparseable request body.
+    $response = $this->request('PATCH', $url, $request_options);
+    $this->assertResourceErrorResponse(400, 'Syntax error', $response);
+
+    $request_options[RequestOptions::BODY] = $parseable_invalid_request_body;
+
     // DX: 422 when invalid entity: multiple values sent for single-value field.
     $response = $this->request('PATCH', $url, $request_options);
     $label_field = $this->entity->getEntityType()->hasKey('label') ? $this->entity->getEntityType()->getKey('label') : static::$labelFieldName;
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/Media/MediaResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Media/MediaResourceTestBase.php
index 475a1ca..f32fa59 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/Media/MediaResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/Media/MediaResourceTestBase.php
@@ -244,10 +244,10 @@ protected function getExpectedUnauthorizedAccessMessage($method) {
         return "The 'view media' permission is required and the media item must be published.";
 
       case 'PATCH':
-        return 'You are not authorized to update this media entity of bundle camelids.';
+        return "As a non-owner of this media item, the 'update any media' permission is required; as an owner of this media, the 'update media' permission is required.";
 
       case 'DELETE':
-        return 'You are not authorized to delete this media entity of bundle camelids.';
+        return "As a non-owner of this media item, the 'delete any media' permission is required; as an owner of this media, the 'delete media' permission is required.";
 
       default:
         return parent::getExpectedUnauthorizedAccessMessage($method);
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/MenuLinkContent/MenuLinkContentResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/MenuLinkContent/MenuLinkContentResourceTestBase.php
index 0b1f967..5ad4ad3 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/MenuLinkContent/MenuLinkContentResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/MenuLinkContent/MenuLinkContentResourceTestBase.php
@@ -185,7 +185,7 @@ protected function getExpectedUnauthorizedAccessMessage($method) {
 
     switch ($method) {
       case 'DELETE':
-        return "You are not authorized to delete this menu_link_content entity.";
+        return "The 'administer menu' permission is required.";
       default:
         return parent::getExpectedUnauthorizedAccessMessage($method);
     }
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/ShortcutSet/ShortcutSetResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/ShortcutSet/ShortcutSetResourceTestBase.php
index 97ae1a6..e8ce93f 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/ShortcutSet/ShortcutSetResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/ShortcutSet/ShortcutSetResourceTestBase.php
@@ -85,4 +85,20 @@ protected function getNormalizedPostEntity() {
     // @todo Update in https://www.drupal.org/node/2300677.
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  protected function getExpectedUnauthorizedAccessMessage($method) {
+    if ($this->config('rest.settings')->get('bc_entity_resource_permissions')) {
+      return parent::getExpectedUnauthorizedAccessMessage($method);
+    }
+
+    switch ($method) {
+      case 'GET':
+        return "The 'access shortcuts' permission is required.";
+      default:
+        return parent::getExpectedUnauthorizedAccessMessage($method);
+    }
+  }
+
 }
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/User/UserResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/User/UserResourceTestBase.php
index d758cf6..ddf6814 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/User/UserResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/User/UserResourceTestBase.php
@@ -255,9 +255,9 @@ protected function getExpectedUnauthorizedAccessMessage($method) {
       case 'GET':
         return "The 'access user profiles' permission is required and the user must be active.";
       case 'PATCH':
-        return "You are not authorized to update this user entity.";
+        return "Users can only update their own account, unless they have the 'administer users' permission.";
       case 'DELETE':
-        return 'You are not authorized to delete this user entity.';
+        return "The 'cancel account' permission is required.";
       default:
         return parent::getExpectedUnauthorizedAccessMessage($method);
     }
diff --git a/core/modules/shortcut/src/ShortcutSetAccessControlHandler.php b/core/modules/shortcut/src/ShortcutSetAccessControlHandler.php
index 3a55f74..87be25d 100644
--- a/core/modules/shortcut/src/ShortcutSetAccessControlHandler.php
+++ b/core/modules/shortcut/src/ShortcutSetAccessControlHandler.php
@@ -20,7 +20,7 @@ class ShortcutSetAccessControlHandler extends EntityAccessControlHandler {
   protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
     switch ($operation) {
       case 'view':
-        return AccessResult::allowedIf($account->hasPermission('access shortcuts'))->cachePerPermissions();
+        return AccessResult::allowedIfHasPermission($account, 'access shortcuts');
       case 'update':
         if ($account->hasPermission('administer shortcuts')) {
           return AccessResult::allowed()->cachePerPermissions();
diff --git a/core/modules/user/src/UserAccessControlHandler.php b/core/modules/user/src/UserAccessControlHandler.php
index 712b32a..4020efb 100644
--- a/core/modules/user/src/UserAccessControlHandler.php
+++ b/core/modules/user/src/UserAccessControlHandler.php
@@ -4,6 +4,7 @@
 
 use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Access\AccessResultNeutral;
+use Drupal\Core\Access\AccessResultReasonInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityAccessControlHandler;
 use Drupal\Core\Field\FieldDefinitionInterface;
@@ -64,11 +65,16 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter
 
       case 'update':
         // Users can always edit their own account.
-        return AccessResult::allowedIf($account->id() == $entity->id())->cachePerUser();
+        $access_result = AccessResult::allowedIf($account->id() == $entity->id())->cachePerUser();
+        if (!$access_result->isAllowed() && $access_result instanceof AccessResultReasonInterface) {
+          $access_result->setReason("Users can only update their own account, unless they have the 'administer users' permission.");
+        }
+        return $access_result;
 
       case 'delete':
         // Users with 'cancel account' permission can cancel their own account.
-        return AccessResult::allowedIf($account->id() == $entity->id() && $account->hasPermission('cancel account'))->cachePerPermissions()->cachePerUser();
+        return AccessResult::allowedIfHasPermission($account, 'cancel account')
+          ->andIf(AccessResult::allowedIf($account->id() == $entity->id())->cachePerUser());
     }
 
     // No opinion.
