diff --git a/core/core.services.yml b/core/core.services.yml
index 3c27130..0e44530 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -833,6 +833,10 @@ services:
     arguments: ['@current_route_match']
     tags:
       - { name: route_processor_outbound, priority: 200 }
+  route_process.system.user_entered_path:
+    class: Drupal\Core\RouteProcessor\RouteProcessorUserEnteredPath
+    tags:
+      - { name: route_processor_outbound, priority: 200 }
   path_processor_alias:
     class: Drupal\Core\PathProcessor\PathProcessorAlias
     tags:
diff --git a/core/lib/Drupal/Core/Path/PathValidator.php b/core/lib/Drupal/Core/Path/PathValidator.php
index e6ebf5d..547a5c7 100644
--- a/core/lib/Drupal/Core/Path/PathValidator.php
+++ b/core/lib/Drupal/Core/Path/PathValidator.php
@@ -74,14 +74,14 @@ public function __construct(AccessAwareRouterInterface $access_aware_router, Url
   /**
    * {@inheritdoc}
    */
-  public function isValid($path) {
-    return (bool) $this->getUrlIfValid($path);
+  public function isValid($path, $access_check = TRUE) {
+    return (bool) $this->getUrlIfValid($path, $access_check);
   }
 
   /**
    * {@inheritdoc}
    */
-  public function getUrlIfValid($path) {
+  public function getUrlIfValid($path, $access_check = TRUE) {
     $parsed_url = UrlHelper::parse($path);
 
     $options = [];
@@ -104,7 +104,7 @@ public function getUrlIfValid($path) {
 
     $path = ltrim($path, '/');
     $request = Request::create('/' . $path);
-    $attributes = $this->getPathAttributes($path, $request);
+    $attributes = $this->getPathAttributes($path, $request, $access_check);
 
     if (!$attributes) {
       return FALSE;
@@ -123,12 +123,15 @@ public function getUrlIfValid($path) {
    *   The path to check.
    * @param \Symfony\Component\HttpFoundation\Request $request
    *   A request object with the given path.
+   * @param bool $access_check
+   *   If FALSE then skip access check and check only whether the path is
+   *   valid.
    *
    * @return array|bool
    *   An array of request attributes of FALSE if an exception was thrown.
    */
-  protected function getPathAttributes($path, Request $request) {
-    if ($this->account->hasPermission('link to any page')) {
+  protected function getPathAttributes($path, Request $request, $access_check) {
+    if (!$access_check || $this->account->hasPermission('link to any page')) {
       $router = $this->accessUnawareRouter;
     }
     else {
diff --git a/core/lib/Drupal/Core/Path/PathValidatorInterface.php b/core/lib/Drupal/Core/Path/PathValidatorInterface.php
index e0bcf04..630a5cc 100644
--- a/core/lib/Drupal/Core/Path/PathValidatorInterface.php
+++ b/core/lib/Drupal/Core/Path/PathValidatorInterface.php
@@ -17,21 +17,27 @@
    *
    * @param string $path
    *   The path to check.
+   * @param bool $access_check
+   *   If FALSE then skip access check and check only whether the path is
+   *   valid.
    *
    * @return \Drupal\Core\Url|false
    *   The url object, or FALSE if the path is not valid.
    */
-  public function getUrlIfValid($path);
+  public function getUrlIfValid($path, $access_check = TRUE);
 
   /**
    * Checks if the URL path is valid and accessible by the current user.
    *
    * @param string $path
    *   The path to check.
+   * @param bool $access_check
+   *   If FALSE then skip access check and check only whether the path is
+   *   valid.
    *
    * @return bool
    *   TRUE if the path is valid.
    */
-  public function isValid($path);
+  public function isValid($path, $access_check = TRUE);
 
 }
diff --git a/core/lib/Drupal/Core/RouteProcessor/RouteProcessorUserEnteredPath.php b/core/lib/Drupal/Core/RouteProcessor/RouteProcessorUserEnteredPath.php
new file mode 100644
index 0000000..9a32c54
--- /dev/null
+++ b/core/lib/Drupal/Core/RouteProcessor/RouteProcessorUserEnteredPath.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\RouteProcessor\RouteProcessorCurrent.
+ */
+
+namespace Drupal\Core\RouteProcessor;
+
+use Symfony\Component\Routing\Route;
+
+/**
+ * Provides a route processor to replace <user_entered_path>/{path}.
+ */
+class RouteProcessorUserEnteredPath implements OutboundRouteProcessorInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processOutbound($route_name, Route $route, array &$parameters) {
+    if ($route_name === 'system.user_entered_path' && isset($parameters['path'])) {
+      $route->setPath($parameters['path']);
+      unset($parameters['path']);
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Routing/UserEnteredPathAccessCheck.php b/core/lib/Drupal/Core/Routing/UserEnteredPathAccessCheck.php
new file mode 100644
index 0000000..96c139b
--- /dev/null
+++ b/core/lib/Drupal/Core/Routing/UserEnteredPathAccessCheck.php
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Routing\UserEnteredPathAccessCheck.
+ */
+
+namespace Drupal\Core\Routing;
+
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\ParamConverter\ParamNotConvertedException;
+use Drupal\Core\Session\AccountInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+
+/**
+ * Access check the system.user_entered_path route.
+ */
+class UserEnteredPathAccessCheck {
+
+  /**
+   * @var \Symfony\Component\Routing\Matcher\RequestMatcherInterface
+   */
+  protected $router;
+
+  /**
+   * @var \Drupal\Core\Access\AccessManagerInterface
+   */
+  protected $accessManager;
+
+  /**
+   * Access check for the system.user_entered_path route.
+   *
+   * @param string $path
+   *   The path entered by the user.
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   Run access checks for this account.
+   *
+   * @return \Drupal\Core\Access\AccessResultInterface
+   */
+  public function access($path, AccountInterface $account) {
+    $request = Request::create('/' . ltrim($path, '/'));
+    try {
+      $parameters = $this->router()->matchRequest($request);
+    }
+    catch (ResourceNotFoundException $e) {
+      return AccessResult::neutral();
+    }
+    catch (ParamNotConvertedException $e) {
+      return AccessResult::neutral();
+    }
+    $request->attributes->add($parameters);
+    return $this->accessManager()->checkRequest($request, $account, TRUE);
+  }
+
+  /**
+   * @return \Symfony\Component\Routing\Matcher\RequestMatcherInterface
+   */
+  public function router() {
+    if (!isset($this->router)) {
+      $this->router = \Drupal::service('router.no_access_checks');
+    }
+    return $this->router;
+  }
+
+  /**
+   * @return \Drupal\Core\Access\AccessManagerInterface
+   */
+  public function accessManager() {
+    if (!isset($this->accessManager)) {
+      $this->accessManager = \Drupal::accessManager();
+    }
+    return $this->accessManager;
+  }
+
+}
diff --git a/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php b/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php
index ca753f8..b569c88 100644
--- a/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php
+++ b/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php
@@ -158,19 +158,10 @@ public function viewElements(FieldItemListInterface $items) {
         }
       }
       else {
-        $element[$delta] = array(
+        $element[$delta] = $url->toRenderArray() + array(
           '#type' => 'link',
           '#title' => $link_title,
-          '#options' => $url->getOptions(),
         );
-        if ($url->isExternal()) {
-          $element[$delta]['#href'] = $url->getUri();
-        }
-        else {
-          $element[$delta]['#route_name'] = $url->getRouteName();
-          $element[$delta]['#route_parameters'] = $url->getRouteParameters();
-        }
-
         if (!empty($item->_attributes)) {
           $element[$delta]['#options'] += array ('attributes' => array());
           $element[$delta]['#options']['attributes'] += $item->_attributes;
diff --git a/core/modules/link/src/Plugin/Field/FieldType/LinkItem.php b/core/modules/link/src/Plugin/Field/FieldType/LinkItem.php
index 37e40d4..0dada6d 100644
--- a/core/modules/link/src/Plugin/Field/FieldType/LinkItem.php
+++ b/core/modules/link/src/Plugin/Field/FieldType/LinkItem.php
@@ -8,6 +8,7 @@
 namespace Drupal\link\Plugin\Field\FieldType;
 
 use Drupal\Component\Utility\Random;
+use Drupal\Component\Utility\UrlHelper;
 use Drupal\Core\Field\FieldDefinitionInterface;
 use Drupal\Core\Field\FieldItemBase;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
@@ -183,4 +184,43 @@ public function isExternal() {
   public static function mainPropertyName() {
     return 'url';
   }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setValue($values, $notify = TRUE) {
+    parent::setValue($values, $notify);
+    if (!is_array($values) || (isset($values['url']) && !isset($values['route_parameters']))) {
+      $this->setRoute(is_array($values) ? $values['url'] : $values);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onChange($property_name) {
+    if ($property_name == 'url') {
+      $this->setRoute($this->get('url')->getValue());
+    }
+    parent::onChange($property_name);
+  }
+
+  /**
+   * @param $url
+   */
+  protected function setRoute($url) {
+    if ($url == '<front>') {
+      $this->set('route_name', '<front>');
+      $this->set('route_parameters', []);
+    }
+    elseif (UrlHelper::isExternal($url)) {
+      $this->set('route_name', '');
+      $this->set('route_parameters', []);
+    }
+    elseif ($this->getFieldDefinition('link_type')->getSetting('link_type') & LinkItemInterface::LINK_INTERNAL) {
+      $this->set('route_name', 'system.user_entered_path');
+      $this->set('route_parameters', ['path' => $url]);
+    }
+  }
+
 }
diff --git a/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php b/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php
index 21e078f..c21dd26 100644
--- a/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php
+++ b/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php
@@ -39,19 +39,19 @@ public static function defaultSettings() {
    * {@inheritdoc}
    */
   public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
-
-    $default_url_value = NULL;
-    if (isset($items[$delta]->url)) {
-      if ($url = \Drupal::pathValidator()->getUrlIfValid($items[$delta]->url)) {
-        $url->setOptions($items[$delta]->options);
-        $default_url_value = ltrim($url->toString(), '/');
+    $default_value = '';
+    if (!empty($items[$delta]->url)) {
+      $default_value = $items[$delta]->url;
+      // Internal, non-accessible but existing paths can not be edited.
+      if (!$items[$delta]->isExternal() && !\Drupal::pathValidator()->isValid($default_value) && \Drupal::pathValidator()->isValid($default_value, FALSE)) {
+        $element['#access'] = FALSE;
       }
     }
     $element['url'] = array(
       '#type' => 'url',
       '#title' => $this->t('URL'),
       '#placeholder' => $this->getSetting('placeholder_url'),
-      '#default_value' => $default_url_value,
+      '#default_value' => $default_value,
       '#maxlength' => 2048,
       '#required' => $element['#required'],
     );
@@ -194,26 +194,4 @@ public function validateTitle(&$element, FormStateInterface $form_state, $form)
     }
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
-    foreach ($values as &$value) {
-      if (!empty($value['url'])) {
-        $url = \Drupal::pathValidator()->getUrlIfValid($value['url']);
-        if (!$url) {
-          return $values;
-        }
-
-        $value += $url->toArray();
-
-        // Reset the URL value to contain only the path.
-        if (!$url->isExternal() && $this->supportsInternalLinks()) {
-          $value['url'] = substr($url->toString(), strlen(\Drupal::request()->getBasePath() . '/'));
-        }
-      }
-    }
-    return $values;
-  }
-
 }
diff --git a/core/modules/link/src/Tests/LinkWidgetTest.php b/core/modules/link/src/Tests/LinkWidgetTest.php
new file mode 100644
index 0000000..7c83944
--- /dev/null
+++ b/core/modules/link/src/Tests/LinkWidgetTest.php
@@ -0,0 +1,148 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\link\Tests\LinkWidgetTest.
+ */
+
+namespace Drupal\link\Tests;
+use Drupal\Core\Url;
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests link widget functionality.
+ *
+ * @group link
+ */
+class LinkWidgetTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('node', 'link');
+
+  /**
+   * @var \Drupal\user\Entity\User
+   */
+  protected $webUser;
+
+  /**
+   * @var \Drupal\node\Entity\NodeType
+   */
+  protected $nodeType;
+
+  /**
+   * @var \Drupal\node\Entity\Node[]
+   */
+  protected $nodes;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->nodeType = $this->drupalCreateContentType();
+    // Create a link field.
+    entity_create('field_storage_config', array(
+      'field_name' => 'field_link',
+      'entity_type' => 'node',
+      'type' => 'link',
+    ))->save();
+    $type = $this->nodeType->type;
+    entity_create('field_config', array(
+      'field_name' => 'field_link',
+      'entity_type' => 'node',
+      'bundle' => $type,
+    ))->save();
+    entity_get_display('node', $type, 'default')
+      ->setComponent('field_link', array(
+        'type' => 'link',
+      ))
+      ->save();
+    entity_get_form_display('node', $type, 'default')
+      ->setComponent('field_link', array(
+        'type' => 'link_default',
+      ))
+      ->save();
+    $this->webUser = $this->drupalCreateUser(array('bypass node access'));
+    $this->drupalLogin($this->webUser);
+    // This wil be used as a link target.
+    $this->nodes[0] = $this->drupalCreateNode(['type' => $type]);
+  }
+
+  /**
+   * Tests the field widget.
+   */
+  public function testLinkWidget() {
+    // Create a link to nodes[0].
+    $link_title = $this->randomMachineName();
+    $path = 'node/' . $this->nodes[0]->id();
+    $node_title = $this->randomString();
+    $edit = array(
+      'title[0][value]' => $node_title,
+      'field_link[0][url]' => $path,
+      'field_link[0][title]' => $link_title,
+    );
+    $this->drupalPostForm('node/add/' . $this->nodeType->type, $edit, t('Save'));
+    $href = $this->nodes[0]->url();
+    $this->assertLinkByLabelAndHref($link_title, $href);
+    $node = $this->drupalGetNodeByTitle($node_title);
+    // Assert the path is intact in the widget.
+    $this->drupalGet('node/'. $node->id() . '/edit');
+    $this->assertFieldByName('field_link[0][url]', $path);
+    // Add an alias.
+    $alias = $this->randomMachineName();
+    \Drupal::service('path.alias_storage')->save($path, $alias);
+    $aliased_href = $this->nodes[0]->url();
+    $this->assertNotEqual($href, $aliased_href);
+    $this->drupalPostForm(NULL, array(), t('Save'));
+    // Now the link points to the alias.
+    $this->assertLinkByLabelAndHref($link_title, $aliased_href);
+    // But the path in the widget is still the same.
+    $this->drupalGet('node/'. $node->id() . '/edit');
+    $this->assertFieldByName('field_link[0][url]', $path);
+    // Now try to link to an alias.
+    $edit = array(
+      'field_link[0][url]' => $alias,
+    );
+    $this->drupalPostForm(NULL, $edit, t('Save'));
+    $this->assertLinkByLabelAndHref($link_title, $aliased_href);
+    // Now delete the alias.
+    \Drupal::service('path.alias_storage')->delete(array('source' => $path));
+    // Assert it doesn't change the user entered value.
+    $this->drupalGet('node/'. $node->id() . '/edit');
+    $this->assertFieldByName('field_link[0][url]', $alias);
+    // Now try to link to the front page.
+    $edit = array(
+      'field_link[0][url]' => '<front>',
+    );
+    $this->drupalPostForm(NULL, $edit, t('Save'));
+    $href = Url::fromRoute('<front>')->toString();
+    $this->assertLinkByLabelAndHref($link_title, $href);
+    // Now create an existing but inaccessible link and ensure it is not
+    // editable but it persists through editing.
+    $node = $this->drupalGetNodeByTitle($node_title, TRUE);
+    $node->field_link->url = 'admin';
+    $node->save();
+    $this->drupalGet('node/'. $node->id() . '/edit');
+    $this->assertNoFieldByName('field_link[0][url]');
+    $this->assertNoFieldByName('field_link[0][title]');
+    $node = $this->drupalGetNodeByTitle($node_title, TRUE);
+    $this->assertEqual($node->field_link->route_name, 'system.user_entered_path');
+    $this->assertEqual($node->field_link->route_parameters, array('path' => 'admin'));
+  }
+
+  /**
+   * Assert a link both by label and href.
+   *
+   * @param $label
+   * @param $href
+   */
+  protected function assertLinkByLabelAndHref($label, $href) {
+    $urls = $this->xpath('//a[normalize-space()=:label and @href=:href]', array(':label' => $label, ':href' => $href));
+    $this->assertEqual(count($urls), 1);
+  }
+
+}
diff --git a/core/modules/rdf/src/Tests/Field/LinkFieldRdfaTest.php b/core/modules/rdf/src/Tests/Field/LinkFieldRdfaTest.php
index 8478bae..78b527c 100644
--- a/core/modules/rdf/src/Tests/Field/LinkFieldRdfaTest.php
+++ b/core/modules/rdf/src/Tests/Field/LinkFieldRdfaTest.php
@@ -95,7 +95,6 @@ public function testAllFormattersFront() {
     // Set up test values.
     $this->testValue = '<front>';
     $this->entity = entity_create('entity_test', array());
-    $this->entity->{$this->fieldName}->route_name = $this->testValue;
     $this->entity->{$this->fieldName}->url = '<front>';
 
     // Set up the expected result.
diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml
index b285bcf..637f281 100644
--- a/core/modules/system/system.routing.yml
+++ b/core/modules/system/system.routing.yml
@@ -440,3 +440,8 @@ system.admin_content:
     _title: 'Content'
   requirements:
     _permission: 'access administration pages'
+
+system.user_entered_path:
+  path: '<user_entered_path>/{path}'
+  requirements:
+    _custom_access: 'Drupal\Core\Routing\UserEnteredPathAccessCheck::access'
