diff --git a/core/authorize.php b/core/authorize.php
index 1138c92..a405ee9 100644
--- a/core/authorize.php
+++ b/core/authorize.php
@@ -120,15 +120,31 @@ function authorize_access_allowed(Request $request) {
       '#messages' => $results['messages'],
     );
 
-    $links = array();
     if (is_array($results['tasks'])) {
-      $links += $results['tasks'];
+      $links = $results['tasks'];
     }
     else {
-      $links = array_merge($links, array(
-        \Drupal::l(t('Administration pages'), new Url('system.admin')),
-        \Drupal::l(t('Front page'), new Url('<front>')),
-      ));
+      // Since this is being called outsite of the primary front controller,
+      // the base_url needs to be set explicitly to ensure that links are
+      // relative to the site root.
+      // @todo Simplify with https://www.drupal.org/node/2548095
+      $default_options = [
+        '#type' => 'link',
+        '#options' => [
+          'absolute' => TRUE,
+          'base_url' => $GLOBALS['base_url'],
+        ],
+      ];
+      $links = [
+        $default_options + [
+          '#url' => new Url('system.admin'),
+          '#title' => t('Administration pages'),
+        ],
+        $default_options + [
+          '#url' => new Url('<front>'),
+          '#title' => t('Front page'),
+        ],
+      ];
     }
 
     $content['next_steps'] = array(
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 7e311d9..5ece44d 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -1747,17 +1747,10 @@ function drupal_common_theme() {
     'maintenance_task_list' => array(
       'variables' => array('items' => NULL, 'active' => NULL,  'variant' => NULL),
     ),
-    'authorize_message' => array(
-      'variables' => array('message' => NULL, 'success' => TRUE),
-      'function' => 'theme_authorize_message',
-      'path' => 'core/includes',
-      'file' => 'theme.maintenance.inc',
-    ),
     'authorize_report' => array(
-      'variables' => array('messages' => array()),
-      'function' => 'theme_authorize_report',
-      'path' => 'core/includes',
-      'file' => 'theme.maintenance.inc',
+      'variables' => ['messages' => [], 'attributes' => []],
+      'includes' => ['core/includes/theme.maintenance.inc'],
+      'template' => 'authorize-report',
     ),
     // From pager.inc.
     'pager' => array(
diff --git a/core/includes/theme.maintenance.inc b/core/includes/theme.maintenance.inc
index d23addb..b98d28a 100644
--- a/core/includes/theme.maintenance.inc
+++ b/core/includes/theme.maintenance.inc
@@ -100,60 +100,37 @@ function _drupal_maintenance_theme() {
 }
 
 /**
- * Returns HTML for a results report of an operation run by authorize.php.
+ * Prepares variables for authorize.php operation report templates.
  *
- * @param $variables
+ * This report displays the results of an operation run via authorize.php.
+ *
+ * Default template: authorize-report.html.twig.
+ *
+ * @param array $variables
  *   An associative array containing:
  *   - messages: An array of result messages.
- *
- * @ingroup themeable
  */
-function theme_authorize_report($variables) {
-  $messages = $variables['messages'];
-  $output = '';
-  if (!empty($messages)) {
-    $output .= '<div class="authorize-results">';
-    foreach ($messages as $heading => $logs) {
-      $items = array();
+function template_preprocess_authorize_report(&$variables) {
+  $messages = [];
+  if (!empty($variables['messages'])) {
+    foreach ($variables['messages'] as $heading => $logs) {
+      $items = [];
       foreach ($logs as $number => $log_message) {
         if ($number === '#abort') {
           continue;
         }
-        $authorize_message = array(
-          '#theme' => 'authorize_message',
-          '#message' => $log_message['message'],
-          '#success' => $log_message['success'],
-        );
-        $items[] = array(
-          '#markup' => drupal_render($authorize_message),
-          '#wrapper_attributes' => array('class' => $log_message['success'] ? 'authorize-results__success' : 'authorize-results__failure'),
-        );
+        $class = 'authorize-results__' . ($log_message['success'] ? 'success' : 'failure');
+        $items[] = [
+          '#wrapper_attributes' => ['class' => [$class]],
+          '#markup' => $log_message['message'],
+        ];
       }
-      $item_list = array(
+      $messages[] = [
         '#theme' => 'item_list',
         '#items' => $items,
         '#title' => $heading,
-      );
-      $output .= drupal_render($item_list);
+      ];
     }
-    $output .= '</div>';
   }
-  return $output;
-}
-
-/**
- * Returns HTML for a single log message from the authorize.php batch operation.
- *
- * @param $variables
- *   An associative array containing:
- *   - message: The log message.
- *     It's the caller's responsibility to ensure this string contains no
- *     dangerous HTML such as SCRIPT tags.
- *   - success: A boolean indicating failure or success.
- *
- * @ingroup themeable
- */
-function theme_authorize_message($variables) {
-  $item = array('#markup' => $variables['message']);
-  return drupal_render($item);
+  $variables['messages'] = $messages;
 }
diff --git a/core/lib/Drupal/Core/Theme/Registry.php b/core/lib/Drupal/Core/Theme/Registry.php
index bb89707..d24802d 100644
--- a/core/lib/Drupal/Core/Theme/Registry.php
+++ b/core/lib/Drupal/Core/Theme/Registry.php
@@ -442,6 +442,13 @@ protected function processExtension(array &$cache, $name, $type, $theme, $path)
           $result[$hook]['includes'] = $cache[$hook]['includes'];
         }
 
+        // Load the includes, as they may contain preprocess functions.
+        if (isset($info['includes'])) {
+          foreach ($info['includes'] as $include_file) {
+            include_once $this->root . '/' . $include_file;
+          }
+        }
+
         // If the theme implementation defines a file, then also use the path
         // that it defined. Otherwise use the default path. This allows
         // system.module to declare theme functions on behalf of core .include
diff --git a/core/lib/Drupal/Core/Updater/Module.php b/core/lib/Drupal/Core/Updater/Module.php
index c495272..ae7bea8 100644
--- a/core/lib/Drupal/Core/Updater/Module.php
+++ b/core/lib/Drupal/Core/Updater/Module.php
@@ -108,11 +108,31 @@ public function getSchemaUpdates() {
    * Overrides Drupal\Core\Updater\Updater::postInstallTasks().
    */
   public function postInstallTasks() {
-    return array(
-      \Drupal::l(t('Install another module'), new Url('update.module_install')),
-      \Drupal::l(t('Enable newly added modules'), new Url('system.modules_list')),
-      \Drupal::l(t('Administration pages'), new Url('system.admin')),
-    );
+    // Since this is being called outsite of the primary front controller,
+    // the base_url needs to be set explicitly to ensure that links are
+    // relative to the site root.
+    // @todo Simplify with https://www.drupal.org/node/2548095
+    $default_options = [
+      '#type' => 'link',
+      '#options' => [
+        'absolute' => TRUE,
+        'base_url' => $GLOBALS['base_url'],
+      ],
+    ];
+    return [
+      $default_options + [
+        '#url' => new Url('update.module_install'),
+        '#title' => t('Install another module'),
+      ],
+      $default_options + [
+        '#url' => new Url('system.modules_list'),
+        '#title' => t('Enable newly added modules'),
+      ],
+      $default_options + [
+        '#url' => new Url('system.admin'),
+        '#title' => t('Administration pages'),
+      ],
+    ];
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Updater/Theme.php b/core/lib/Drupal/Core/Updater/Theme.php
index 519f110..afc3bab 100644
--- a/core/lib/Drupal/Core/Updater/Theme.php
+++ b/core/lib/Drupal/Core/Updater/Theme.php
@@ -91,9 +91,26 @@ public function postInstall() {
    * Overrides Drupal\Core\Updater\Updater::postInstallTasks().
    */
   public function postInstallTasks() {
-    return array(
-      \Drupal::l(t('Install newly added themes'), new Url('system.themes_page')),
-      \Drupal::l(t('Administration pages'), new Url('system.admin')),
-    );
+    // Since this is being called outsite of the primary front controller,
+    // the base_url needs to be set explicitly to ensure that links are
+    // relative to the site root.
+    // @todo Simplify with https://www.drupal.org/node/2548095
+    $default_options = [
+      '#type' => 'link',
+      '#options' => [
+        'absolute' => TRUE,
+        'base_url' => $GLOBALS['base_url'],
+      ],
+    ];
+    return [
+      $default_options + [
+        '#url' => new Url('system.themes_page'),
+        '#title' => t('Install newly added themes'),
+      ],
+      $default_options + [
+        '#url' => new Url('system.admin'),
+        '#title' => t('Administration pages'),
+      ],
+    ];
   }
 }
diff --git a/core/modules/system/templates/authorize-report.html.twig b/core/modules/system/templates/authorize-report.html.twig
new file mode 100644
index 0000000..9144586
--- /dev/null
+++ b/core/modules/system/templates/authorize-report.html.twig
@@ -0,0 +1,23 @@
+{#
+/**
+ * @file
+ * Default theme implementation for authorize.php operation report templates.
+ *
+ * This report displays the results of an operation run via authorize.php.
+ *
+ * Available variables:
+ * - messages: A list of result messages.
+ * - attributes: HTML attributes for the element.
+ *
+ * @see template_preprocess_authorize_report()
+ *
+ * @ingroup themeable
+ */
+#}
+{% if messages %}
+  <div{{ attributes.addClass('authorize-results') }}>
+    {% for message_group in messages %}
+      {{ message_group }}
+    {% endfor %}
+  </div>
+{% endif %}
diff --git a/core/modules/update/src/Tests/UpdateUploadTest.php b/core/modules/update/src/Tests/UpdateUploadTest.php
index c568fa5..9f05d88 100644
--- a/core/modules/update/src/Tests/UpdateUploadTest.php
+++ b/core/modules/update/src/Tests/UpdateUploadTest.php
@@ -75,6 +75,10 @@ public function testUploadModule() {
     // module now exists in the expected place in the filesystem.
     $this->assertRaw(t('Installed %project_name successfully', array('%project_name' => 'update_test_new_module')));
     $this->assertTrue(file_exists($installedInfoFilePath), 'The new module exists in the filesystem after it is installed with the Update Manager.');
+    // Check that the link to install another module appears on the page and
+    // that the user can access it.
+    $this->clickLink(t('Install another module'));
+    $this->assertResponse(200);
   }
 
   /**
diff --git a/core/modules/update/update.authorize.inc b/core/modules/update/update.authorize.inc
index cead4f0..b998607 100644
--- a/core/modules/update/update.authorize.inc
+++ b/core/modules/update/update.authorize.inc
@@ -11,6 +11,7 @@
  */
 
 use Drupal\Core\Updater\UpdaterException;
+use Drupal\Core\Url;
 
 /**
  * Updates existing projects when invoked by authorize.php.
@@ -239,7 +240,19 @@ function update_authorize_update_batch_finished($success, $results) {
   // Since we're doing an update of existing code, always add a task for
   // running update.php.
   $results['tasks'][] = t('Your modules have been downloaded and updated.');
-  $results['tasks'][] = t('<a href="@update">Run database updates</a>', array('@update' => \Drupal::url('system.db_update')));
+  $results['tasks'][] = [
+    '#type' => 'link',
+    '#url' => new Url('system.db_update'),
+    '#title' => t('Run database updates'),
+    // Since this is being called outsite of the primary front controller,
+    // the base_url needs to be set explicitly to ensure that links are
+    // relative to the site root.
+    // @todo Simplify with https://www.drupal.org/node/2548095
+    '#options' => [
+      'absolute' => TRUE,
+      'base_url' => $GLOBALS['base_url'],
+    ],
+  ];
 
   // Unset the variable since it is no longer needed.
   unset($_SESSION['maintenance_mode']);
