diff --git a/core/authorize.php b/core/authorize.php
index dbab02f..02f7a11 100644
--- a/core/authorize.php
+++ b/core/authorize.php
@@ -130,7 +130,13 @@ function authorize_access_allowed() {
   }
   // If a batch is running, let it run.
   elseif ($request->query->has('batch')) {
-    $content = ['#markup' => _batch_page($request)];
+    $content = _batch_page($request);
+    // If _batch_page() returns a response object (likely a JsonResponse for
+    // JavaScript-based batch processing), send it immediately.
+    if ($content instanceof Response) {
+      $content->send();
+      exit;
+    }
   }
   else {
     if (empty($_SESSION['authorize_operation']) || empty($_SESSION['authorize_filetransfer_info'])) {
diff --git a/core/lib/Drupal/Core/FileTransfer/Form/FileTransferAuthorizeForm.php b/core/lib/Drupal/Core/FileTransfer/Form/FileTransferAuthorizeForm.php
index c8ae6cb..c00bfcd 100644
--- a/core/lib/Drupal/Core/FileTransfer/Form/FileTransferAuthorizeForm.php
+++ b/core/lib/Drupal/Core/FileTransfer/Form/FileTransferAuthorizeForm.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Render\Element;
 use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Response;
 
 /**
  * Provides the file transfer authorization form.
@@ -226,7 +227,10 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
           $filetransfer = $this->getFiletransfer($filetransfer_backend, $form_connection_settings[$filetransfer_backend]);
 
           // Now run the operation.
-          $this->runOperation($filetransfer);
+          $response = $this->runOperation($filetransfer);
+          if ($response instanceof Response) {
+            $form_state->setResponse($response);
+          }
         }
         catch (\Exception $e) {
           // If there is no database available, we don't care and just skip
@@ -333,13 +337,18 @@ protected function setConnectionSettingsDefaults(&$element, $key, array $default
    *
    * @param $filetransfer
    *   The FileTransfer object to use for running the operation.
+   *
+   * @return mixed
+   *   The result of running the operation. If this is an instance of
+   *   \Symfony\Component\HttpFoundation\Response the calling code should use
+   *   that response for the current page request.
    */
   protected function runOperation($filetransfer) {
     $operation = $_SESSION['authorize_operation'];
     unset($_SESSION['authorize_operation']);
 
     require_once $this->root . '/' . $operation['file'];
-    call_user_func_array($operation['callback'], array_merge(array($filetransfer), $operation['arguments']));
+    return call_user_func_array($operation['callback'], array_merge(array($filetransfer), $operation['arguments']));
   }
 
 }
diff --git a/core/modules/update/src/Form/UpdateManagerInstall.php b/core/modules/update/src/Form/UpdateManagerInstall.php
index b3e2deb..fefeeea 100644
--- a/core/modules/update/src/Form/UpdateManagerInstall.php
+++ b/core/modules/update/src/Form/UpdateManagerInstall.php
@@ -13,6 +13,7 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Updater\Updater;
 use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Response;
 
 /**
  * Configure update settings for this site.
@@ -219,7 +220,10 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
     if (fileowner($project_real_location) == fileowner(conf_path())) {
       $this->moduleHandler->loadInclude('update', 'inc', 'update.authorize');
       $filetransfer = new Local($this->root);
-      call_user_func_array('update_authorize_run_install', array_merge(array($filetransfer), $arguments));
+      $response = call_user_func_array('update_authorize_run_install', array_merge(array($filetransfer), $arguments));
+      if ($response instanceof Response) {
+        $form_state->setResponse($response);
+      }
     }
 
     // Otherwise, go through the regular workflow to prompt for FTP/SSH
diff --git a/core/modules/update/src/Form/UpdateReady.php b/core/modules/update/src/Form/UpdateReady.php
index 8e106fe..d21b89b 100644
--- a/core/modules/update/src/Form/UpdateReady.php
+++ b/core/modules/update/src/Form/UpdateReady.php
@@ -14,6 +14,7 @@
 use Drupal\Core\State\StateInterface;
 use Drupal\Core\Updater\Updater;
 use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Response;
 
 /**
  * Configure update settings for this site.
@@ -145,7 +146,10 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
       if (fileowner($project_real_location) == fileowner(conf_path())) {
         $this->moduleHandler->loadInclude('update', 'inc', 'update.authorize');
         $filetransfer = new Local($this->root);
-        update_authorize_run_update($filetransfer, $updates);
+        $response = update_authorize_run_update($filetransfer, $updates);
+        if ($response instanceof Response) {
+          $form_state->setResponse($response);
+        }
       }
       // Otherwise, go through the regular workflow to prompt for FTP/SSH
       // credentials and invoke update_authorize_run_update() indirectly with
diff --git a/core/modules/update/update.authorize.inc b/core/modules/update/update.authorize.inc
index d6a7066..a439a10 100644
--- a/core/modules/update/update.authorize.inc
+++ b/core/modules/update/update.authorize.inc
@@ -28,6 +28,11 @@
  *   - updater_name: The name of the Drupal\Core\Updater\Updater class to use
  *     for this project.
  *   - local_url: The locally installed location of new code to update with.
+ *
+ * @return mixed
+ *   The result of processing the batch that updates the projects. If this is
+ *   an instance of \Symfony\Component\HttpFoundation\Response the calling code
+ *   should use that response for the current page request.
  */
 function update_authorize_run_update($filetransfer, $projects) {
   $operations = array();
@@ -50,8 +55,8 @@ function update_authorize_run_update($filetransfer, $projects) {
     'finished' => 'update_authorize_update_batch_finished',
     'file' => drupal_get_path('module', 'update') . '/update.authorize.inc',
   );
-
   batch_set($batch);
+
   // Invoke the batch via authorize.php.
   return system_authorized_batch_process();
 }
@@ -74,6 +79,11 @@ function update_authorize_run_update($filetransfer, $projects) {
  * @param string $local_url
  *   The URL to the locally installed temp directory where the project has
  *   already been downloaded and extracted into.
+ *
+ * @return mixed
+ *   The result of processing the batch that installs the projects. If this is
+ *   an instance of \Symfony\Component\HttpFoundation\Response the calling code
+ *   should use that response for the current page request.
  */
 function update_authorize_run_install($filetransfer, $project, $updater_name, $local_url) {
   $operations[] = array(
