Allow authorization via cookies
I tried many different ways of loading the user, using the session as the starting point. In every singe scenario it fell down.
So in the end I used as much of the Drupal scaffold as I could, and copied the relevant bits from the relevant classes and functions in order to control the loading of a user record in an ajax handler that exists outside of Drupal's page space.
It uses the cookie to load the session, retrieve the user id, details and roles and create an account object before setting the bootstrapped Drupal kernel current user to the account object.
We deal with access control at a higher level (relying on \Drupal::currentUser() and leveraging $user-&gt;isAuthenticated() for example) so this script gets up to a consistent point in both contexts (inside Drupal, and non-drupal pages inside the domain) for user management etc.
&lt;?php

/**
 * @file
 * Bootstraps the Drupal kernel and loads the user for the incoming request via cookie
 */

use Drupal\Core\DrupalKernel;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\UserSession;
use Symfony\Component\HttpFoundation\Request;

function _find_autoloader($dir) {
  $autoloadFile = $dir . '/vendor/autoload.php';
  if (file_exists($autoloadFile)) {
    return include_once($autoloadFile);
  }
  elseif (empty($dir) || $dir === DIRECTORY_SEPARATOR) {
    return FALSE;
  }
  return _find_autoloader(dirname($dir));
}

// Immediately return if classes are discoverable (already booted).
if (class_exists('\Drupal\Core\DrupalKernel') &amp;&amp; class_exists('\Drupal')) {
  return \Drupal::service('kernel');
}

// Load autoload
$autoloader = _find_autoloader(empty($_SERVER['PWD']) ? getcwd() : $_SERVER['PWD']); // defined in start.inc
if (!$autoloader || !class_exists('\Drupal\Core\DrupalKernel')) {
  print &quot;This script must be invoked inside a Drupal 8 environment. Unable to continue.\n&quot;;
  exit();
}

const MAINTENANCE_MODE = 'update'; // Don't bother theming

if (PHP_SAPI == 'cli') { // CLI won't have a cookie, stop now
  return;
}

$request = Request::createFromGlobals();
$kernel = DrupalKernel::createFromRequest($request, $autoloader, 'prod');
$kernel-&gt;boot();
$connection = $kernel-&gt;getContainer()-&gt;get('database');

// Get session id from session cookie
$sid = reset($_COOKIE);

// Get uid from sessions, get user from uid
$query = $connection-&gt;queryRange('SELECT uid FROM {sessions} WHERE sid = :sid', 0, 1, [':sid' =&gt; Crypt::hashBase64($sid)]);
$uid = (string)$query-&gt;fetchField();
$values = $connection-&gt;query('SELECT * FROM {users_field_data} u WHERE u.uid = :uid AND u.default_langcode = 1', [':uid' =&gt; $uid])-&gt;fetchAssoc();

// Get roles from uid, and set account
if (!empty($values) &amp;&amp; $values['status'] == 1) {
  $rids = $connection-&gt;query('SELECT roles_target_id FROM {user__roles} WHERE entity_id = :uid', [':uid' =&gt; $values['uid']])-&gt;fetchCol();
  $values['roles'] = array_merge([AccountInterface::AUTHENTICATED_ROLE], $rids);
  $account = new UserSession($values);
  \Drupal::currentUser()-&gt;setAccount($account);
}