diff --git a/core/modules/media_library/media_library.services.yml b/core/modules/media_library/media_library.services.yml
index 1a35b2805d..b2d06643b5 100644
--- a/core/modules/media_library/media_library.services.yml
+++ b/core/modules/media_library/media_library.services.yml
@@ -1,7 +1,7 @@
 services:
   media_library.ui_builder:
     class: Drupal\media_library\MediaLibraryUiBuilder
-    arguments: ['@entity_type.manager', '@request_stack', '@views.executable', '@form_builder']
+    arguments: ['@entity_type.manager', '@request_stack', '@views.executable', '@form_builder', '@media_library.opener_resolver']
   media_library.route_subscriber:
     class: Drupal\media_library\Routing\RouteSubscriber
     tags:
@@ -12,3 +12,4 @@ services:
       - [setContainer, ['@service_container']]
   media_library.opener.field_widget:
     class: Drupal\media_library\MediaLibraryFieldWidgetOpener
+    arguments: ['@entity_type.manager']
diff --git a/core/modules/media_library/src/MediaLibraryFieldWidgetOpener.php b/core/modules/media_library/src/MediaLibraryFieldWidgetOpener.php
index 71ef10b2f2..3460996f17 100644
--- a/core/modules/media_library/src/MediaLibraryFieldWidgetOpener.php
+++ b/core/modules/media_library/src/MediaLibraryFieldWidgetOpener.php
@@ -2,8 +2,12 @@
 
 namespace Drupal\media_library;
 
+use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Ajax\AjaxResponse;
 use Drupal\Core\Ajax\InvokeCommand;
+use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Entity\FieldableEntityInterface;
 use Drupal\Core\Session\AccountInterface;
 
 /**
@@ -11,11 +15,81 @@
  */
 class MediaLibraryFieldWidgetOpener implements MediaLibraryOpenerInterface {
 
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * MediaLibraryFieldWidgetOpener constructor.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   */
+  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
+    $this->entityTypeManager = $entity_type_manager;
+  }
+
   /**
    * {@inheritdoc}
    */
   public function checkAccess(MediaLibraryState $state, AccountInterface $account) {
-    throw new \Exception('Not yet implemented, see https://www.drupal.org/project/drupal/issues/3038254.');
+    $parameters = $state->getOpenerParameters() + ['entity_id' => NULL];
+
+    // Forbid access if any of the required parameters are missing.
+    foreach (['entity_type_id', 'bundle', 'field_name'] as $key) {
+      if (empty($parameters[$key])) {
+        return AccessResult::forbidden("$key parameter is missing.")
+          ->addCacheContexts(['url.query_args']);
+      }
+    }
+
+    $entity_type_id = $parameters['entity_type_id'];
+    $bundle = $parameters['bundle'];
+    $field_name = $parameters['field_name'];
+
+    // Since we defer to a field to determine access, ensure we are dealing with
+    // a fieldable entity type.
+    $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
+    if (!$entity_type->entityClassImplements(FieldableEntityInterface::class)) {
+      throw new \LogicException("The media library can only be opened by fieldable entities.");
+    }
+
+    /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
+    $storage = $this->entityTypeManager->getStorage($entity_type_id);
+
+    // Load the entity if we have an ID, or create it in memory otherwise.
+    if ($parameters['entity_id']) {
+      /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */
+      $entity = $storage->load($parameters['entity_id']);
+    }
+    else {
+      $values = [];
+      if ($bundle_key = $entity_type->getKey('bundle')) {
+        $values[$bundle_key] = $bundle;
+      }
+      /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */
+      $entity = $storage->create($values);
+    }
+    $items = $entity->get($field_name);
+    $field_definition = $items->getFieldDefinition();
+
+    if ($field_definition->getType() !== 'entity_reference') {
+      throw new \LogicException('The media library can only be opened on entity reference fields.');
+    }
+    if ($field_definition->getFieldStorageDefinition()->getSetting('target_type') !== 'media') {
+      throw new \LogicException('The media library can only be opened on entity reference fields that target media items.');
+    }
+
+    $result = $this->entityTypeManager->getAccessControlHandler($entity_type_id)
+      ->fieldAccess('edit', $field_definition, $account, $items, TRUE);
+
+    if ($result instanceof RefinableCacheableDependencyInterface) {
+      $result->addCacheContexts(['url.query_args']);
+    }
+    return $result;
   }
 
   /**
diff --git a/core/modules/media_library/src/MediaLibraryUiBuilder.php b/core/modules/media_library/src/MediaLibraryUiBuilder.php
index 58632fdfcc..7783d40ff3 100644
--- a/core/modules/media_library/src/MediaLibraryUiBuilder.php
+++ b/core/modules/media_library/src/MediaLibraryUiBuilder.php
@@ -53,6 +53,13 @@ class MediaLibraryUiBuilder {
    */
   protected $viewsExecutableFactory;
 
+  /**
+   * The media library opener resolver.
+   *
+   * @var \Drupal\media_library\OpenerResolverInterface
+   */
+  protected $openerResolver;
+
   /**
    * Constructs a MediaLibraryUiBuilder instance.
    *
@@ -64,12 +71,19 @@ class MediaLibraryUiBuilder {
    *   The views executable factory.
    * @param \Drupal\Core\Form\FormBuilderInterface $form_builder
    *   The currently active request object.
+   * @param \Drupal\media_library\OpenerResolverInterface $opener_resolver
+   *   The opener resolver.
    */
-  public function __construct(EntityTypeManagerInterface $entity_type_manager, RequestStack $request_stack, ViewExecutableFactory $views_executable_factory, FormBuilderInterface $form_builder) {
+  public function __construct(EntityTypeManagerInterface $entity_type_manager, RequestStack $request_stack, ViewExecutableFactory $views_executable_factory, FormBuilderInterface $form_builder, OpenerResolverInterface $opener_resolver = NULL) {
     $this->entityTypeManager = $entity_type_manager;
     $this->request = $request_stack->getCurrentRequest();
     $this->viewsExecutableFactory = $views_executable_factory;
     $this->formBuilder = $form_builder;
+    if (!$opener_resolver) {
+      @trigger_error('The media_library.opener_resolver service must be passed to ' . __METHOD__ . ' and will be required before Drupal 9.0.0.', E_USER_DEPRECATED);
+      $opener_resolver = \Drupal::service('media_library.opener_resolver');
+    }
+    $this->openerResolver = $opener_resolver;
   }
 
   /**
@@ -159,7 +173,7 @@ protected function buildLibraryContent(MediaLibraryState $state) {
    * Check access to the media library.
    *
    * @param \Drupal\Core\Session\AccountInterface $account
-   *   (optional) Run access checks for this account.
+   *   Run access checks for this account.
    * @param \Drupal\media_library\MediaLibraryState $state
    *   (optional) The current state of the media library, derived from the
    *   current request.
@@ -167,10 +181,10 @@ protected function buildLibraryContent(MediaLibraryState $state) {
    * @return \Drupal\Core\Access\AccessResult
    *   The access result.
    */
-  public function checkAccess(AccountInterface $account = NULL, MediaLibraryState $state = NULL) {
+  public function checkAccess(AccountInterface $account, MediaLibraryState $state = NULL) {
     if (!$state) {
       try {
-        MediaLibraryState::fromRequest($this->request);
+        $state = MediaLibraryState::fromRequest($this->request);
       }
       catch (BadRequestHttpException $e) {
         return AccessResult::forbidden($e->getMessage());
@@ -189,8 +203,16 @@ public function checkAccess(AccountInterface $account = NULL, MediaLibraryState
       return AccessResult::forbidden('The media library widget display does not exist.')
         ->addCacheableDependency($view);
     }
-    return AccessResult::allowedIfHasPermission($account, 'view media')
+
+    // The user must at least be able to view media in order to access the media
+    // library.
+    $can_view_media = AccessResult::allowedIfHasPermission($account, 'view media')
       ->addCacheableDependency($view);
+
+    // Delegate any further access checking to the opener service nominated by
+    // the media library state.
+    return $this->openerResolver->get($state)->checkAccess($state, $account)
+      ->andIf($can_view_media);
   }
 
   /**
diff --git a/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php
index e612c3e4f4..419cf66174 100644
--- a/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php
+++ b/core/modules/media_library/src/Plugin/Field/FieldWidget/MediaLibraryWidget.php
@@ -461,9 +461,17 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
     // This particular media library opener needs some extra metadata for its
     // \Drupal\media_library\MediaLibraryOpenerInterface::getSelectionResponse()
     // to be able to target the element whose 'data-media-library-widget-value'
-    // attribute is the same as $field_widget_id.
+    // attribute is the same as $field_widget_id. The entity ID, entity type ID,
+    // bundle, field name are used for access checking.
+    $entity = $items->getEntity();
     $state = MediaLibraryState::create('media_library.opener.field_widget', $allowed_media_type_ids, $selected_type_id, $remaining, [
       'field_widget_id' => $field_widget_id,
+      'entity_type_id' => $entity->getEntityTypeId(),
+      'bundle' => $entity->bundle(),
+      'field_name' => $field_name,
+      // The entity ID needs to be a string to ensure that the media library
+      // state generates its tamper-proof hash in a consistent way.
+      'entity_id' => (string) $entity->id(),
     ]);
 
     // Add a button that will load the Media library in a modal using AJAX.
diff --git a/core/modules/media_library/tests/modules/media_library_test/media_library_test.module b/core/modules/media_library/tests/modules/media_library_test/media_library_test.module
new file mode 100644
index 0000000000..ab005eaa3c
--- /dev/null
+++ b/core/modules/media_library/tests/modules/media_library_test/media_library_test.module
@@ -0,0 +1,13 @@
+<?php
+
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldItemListInterface;
+Use Drupal\Core\Session\AccountInterface;
+
+/**
+ * Implements hook_entity_field_access().
+ */
+function media_library_test_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, FieldItemListInterface $items = NULL) {
+  return AccessResult::forbiddenIf($field_definition->getName() === 'field_media_no_access', 'Denied by test module');
+}
diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
index 9d54f6a43a..824f2776d8 100644
--- a/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
+++ b/core/modules/media_library/tests/src/FunctionalJavascript/MediaLibraryTest.php
@@ -3,6 +3,9 @@
 namespace Drupal\Tests\media_library\FunctionalJavascript;
 
 use Drupal\Core\Url;
+use Drupal\entity_test\Entity\EntityTestBundle;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
 use Drupal\field_ui\FieldUI;
 use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
 use Drupal\media\Entity\Media;
@@ -28,6 +31,7 @@ class MediaLibraryTest extends WebDriverTestBase {
    */
   protected static $modules = [
     'block',
+    'entity_test',
     'media_library_test',
     'field_ui',
     'views',
@@ -328,9 +332,32 @@ public function testWidgetAccess() {
     $role->revokePermission('view media');
     $role->save();
 
+    EntityTestBundle::create(['id' => 'test_type'])->save();
+
+    $field_storage = FieldStorageConfig::create([
+      'entity_type' => 'entity_test',
+      'type' => 'entity_reference',
+      'field_name' => 'field_test_media',
+      'settings' => [
+        'target_type' => 'media',
+      ],
+    ]);
+    $field_storage->save();
+
+    FieldConfig::create([
+      'field_storage' => $field_storage,
+      'bundle' => 'test_type',
+    ])->save();
+
     // Create a working state.
     $allowed_types = ['type_one', 'type_two', 'type_three', 'type_four'];
-    $state = MediaLibraryState::create('test', $allowed_types, 'type_three', 2);
+    // The opener parameters are not relevant to the test, but the opener
+    // expects them to be there or it will deny access.
+    $state = MediaLibraryState::create('media_library.opener.field_widget', $allowed_types, 'type_three', 2, [
+      'entity_type_id' => 'entity_test',
+      'bundle' => 'test_type',
+      'field_name' => 'field_test_media',
+    ]);
     $url_options = ['query' => $state->all()];
 
     // Verify that unprivileged users can't access the widget view.
diff --git a/core/modules/media_library/tests/src/Kernel/MediaLibraryAccessTest.php b/core/modules/media_library/tests/src/Kernel/MediaLibraryAccessTest.php
index 2b83916e56..8ea949dd3c 100644
--- a/core/modules/media_library/tests/src/Kernel/MediaLibraryAccessTest.php
+++ b/core/modules/media_library/tests/src/Kernel/MediaLibraryAccessTest.php
@@ -2,9 +2,13 @@
 
 namespace Drupal\Tests\media_library\Kernel;
 
+use Drupal\entity_test\Entity\EntityTestBundle;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
 use Drupal\image\Entity\ImageStyle;
 use Drupal\KernelTests\KernelTestBase;
 use Drupal\media_library\MediaLibraryState;
+use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
 use Drupal\Tests\user\Traits\UserCreationTrait;
 use Drupal\views\Views;
 
@@ -15,14 +19,18 @@
  */
 class MediaLibraryAccessTest extends KernelTestBase {
 
+  use MediaTypeCreationTrait;
   use UserCreationTrait;
 
   /**
    * {@inheritdoc}
    */
   protected static $modules = [
+    'entity_test',
     'media',
     'media_library',
+    'media_library_test',
+    'media_test_source',
     'file',
     'field',
     'image',
@@ -78,14 +86,39 @@ public function testMediaLibraryAccess() {
     /** @var \Drupal\media_library\MediaLibraryUiBuilder $ui_builder */
     $ui_builder = $this->container->get('media_library.ui_builder');
 
-    // Create a media library state to test access.
-    $state = MediaLibraryState::create('test', ['file', 'image'], 'file', 2);
+    $media_type = $this->createMediaType('test')->id();
+
+    EntityTestBundle::create(['id' => 'test_type'])->save();
+
+    $field_storage = FieldStorageConfig::create([
+      'entity_type' => 'entity_test',
+      'type' => 'entity_reference',
+      'field_name' => 'field_media_test',
+      'settings' => [
+        'target_type' => 'media',
+      ],
+    ]);
+    $field_storage->save();
+
+    FieldConfig::create([
+      'field_storage' => $field_storage,
+      'bundle' => 'test_type',
+    ])->save();
+
+    // Create a media library state to test access. The opener parameters are
+    // not relevant in this test, but the opener expects them to be there and
+    // will deny access if they aren't.
+    $state = MediaLibraryState::create('media_library.opener.field_widget', [$media_type], $media_type, 2, [
+      'entity_type_id' => 'entity_test',
+      'bundle' => 'test_type',
+      'field_name' => $field_storage->getName(),
+    ]);
 
     // Create a clone of the view so we can reset the original later.
     $view_original = clone Views::getView('media_library');
 
     // Create our test users.
-    $forbidden_account = $this->createUser([]);
+    $forbidden_account = $this->createUser();
     $allowed_account = $this->createUser(['view media']);
 
     // Assert the 'view media' permission is needed to access the library and
@@ -94,7 +127,7 @@ public function testMediaLibraryAccess() {
     $this->assertFalse($access_result->isAllowed());
     $this->assertSame("The 'view media' permission is required.", $access_result->getReason());
     $this->assertSame($view_original->storage->getCacheTags(), $access_result->getCacheTags());
-    $this->assertSame(['user.permissions'], $access_result->getCacheContexts());
+    $this->assertEquals(['url.query_args', 'user.permissions'], $access_result->getCacheContexts());
 
     // Assert that the media library access is denied when the view widget
     // display is deleted.
@@ -115,7 +148,38 @@ public function testMediaLibraryAccess() {
     $access_result = $ui_builder->checkAccess($allowed_account, $state);
     $this->assertTrue($access_result->isAllowed());
     $this->assertSame($view_original->storage->getCacheTags(), $access_result->getCacheTags());
-    $this->assertSame(['user.permissions'], $access_result->getCacheContexts());
+    $this->assertEquals(['url.query_args', 'user.permissions'], $access_result->getCacheContexts());
+
+    // Assert that field access hooks influence access to the media library.
+    // @see media_library_test_entity_field_access()
+    $field_storage = FieldStorageConfig::create([
+      'type' => 'entity_reference',
+      'settings' => [
+        'target_type' => 'media',
+      ],
+      'field_name' => 'field_media_no_access',
+      'entity_type' => 'entity_test',
+    ]);
+    $field_storage->save();
+    FieldConfig::create([
+      'field_storage' => $field_storage,
+      'bundle' => 'test_type',
+    ])->save();
+
+    $opener_parameters = $state->getOpenerParameters();
+    $opener_parameters['field_name'] = 'field_media_no_access';
+    $deny_state = MediaLibraryState::create(
+      $state->getOpenerId(),
+      $state->getAllowedTypeIds(),
+      $state->getSelectedTypeId(),
+      $state->getAvailableSlots(),
+      $opener_parameters
+    );
+    $access_result = $ui_builder->checkAccess($allowed_account, $deny_state);
+    $this->assertFalse($access_result->isAllowed());
+    $this->assertSame('Denied by test module', $access_result->getReason());
+    $this->assertSame([], $access_result->getCacheTags());
+    $this->assertSame(['url.query_args'], $access_result->getCacheContexts());
 
     // Assert that the media library access is denied when the entire media
     // library view is deleted.
