The OAuth response returned from Apple is via a cross-site POST (response_mode=form_post). XenForo sets no SameSite attribute on its session cookies, so browsers treat them as SameSite=Lax by default (Lax cookies are withheld on cross-site POSTs (at least for chromium browsers like Chrome and Edge)). In this case, the browser can't tell Apple's POST apart from a malicious CSRF attempt. The xf_session cookie never reaches connected_account.php; XenForo sees the request as an anonymous session with no pending connect request and therefore we get the "There is no valid connected account request available."
I was able to reproduce this on my board as well and here are my two workarounds:
1) Modify the src/config.php file to set the SameSite config to None (e.g.
$config['cookie']['samesite'] = 'None';)
2) Capture the request and reissue it as a GET request.
Option #1 feels wrong, arguably decreasing the overall security posture of the site just for Apple.
Option #2 is hacky, but you can get things working by adding the following to connected_account.php. The code takes the POST request and turns it into a GET request back to itself (via 303 redirect), so the browser can send the session cookie back and complete the auth flow. Just note the change is lost on XenForo upgrade.
Search for:
$connectedAccountRequest = $session->get('connectedAccountRequest');
Add this below it:
PHP:
if ($request->isPost() && !$connectedAccountRequest)
{
$code = (string) $request->filter('code', 'str');
$error = (string) $request->filter('error', 'str');
if ($code !== '' || $error !== '')
{
$params = [];
foreach (['code', 'state', 'error', 'error_description', 'denied'] AS $bounceKey)
{
$bounceValue = (string) $request->filter($bounceKey, 'str');
if ($bounceValue !== '')
{
$params[$bounceKey] = $bounceValue;
}
}
$bounceUrl = $app->options()->boardUrl . '/connected_account.php';
if ($params)
{
$bounceUrl .= '?' . http_build_query($params);
}
$response->header('Referrer-Policy', 'no-referrer');
$response->redirect($bounceUrl, 303);
$response->send($request);
exit;
}
}