phacility/phabricator · error · Exception

Expected token to finish OAuth handshake!

Error message

Expected token to finish OAuth handshake!

What it means

finishOAuthHandshake() refuses to run when the adapter has no request token stored. The OAuth 1.0a flow is two-phase: getAuthenticateURI() first obtains and stores a request token (via readTokenAndTokenSecret), the user is redirected, and only then can the handshake be finished with that token plus the verifier. This exception means the second phase was entered without the first phase having happened in this adapter instance.

Source

Thrown at src/applications/auth/adapter/PhutilOAuth1AuthAdapter.php:166

    $confirmed = idx($data, 'oauth_callback_confirmed');
    if ($confirmed !== 'true') {
      throw new Exception(
        pht("Expected '%s' to be '%s'!", 'oauth_callback_confirmed', 'true'));
    }

    $this->readTokenAndTokenSecret($data);

    $authorize_token_uri = new PhutilURI($this->getAuthorizeTokenURI());
    $authorize_token_uri->replaceQueryParam('oauth_token', $this->getToken());

    return phutil_string_cast($authorize_token_uri);
  }

  protected function finishOAuthHandshake() {
    $this->willFinishOAuthHandshake();

    if (!$this->getToken()) {
      throw new Exception(pht('Expected token to finish OAuth handshake!'));
    }
    if (!$this->getVerifier()) {
      throw new Exception(pht('Expected verifier to finish OAuth handshake!'));
    }

    $validate_uri = $this->getValidateTokenURI();
    $params = array(
      'oauth_verifier' => $this->getVerifier(),
    );

    list($body) = $this->newOAuth1Future($validate_uri, $params)->resolvex();
    $data = id(new PhutilQueryStringParser())->parseQueryString($body);

    $this->readTokenAndTokenSecret($data);

    $this->handshakeData = $data;
  }

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Ensure getAuthenticateURI() is called first and the request token it stores is persisted (e.g., in the auth workflow state) across the redirect.
  2. On the callback request, restore the token into the adapter with setToken() before triggering the handshake completion.
  3. Check that the provider's oauth_token redirect parameter is actually reaching your code and being applied.
  4. Verify you are not constructing a brand-new adapter instance on the callback and expecting its token to survive.

Example fix

// before: finish phase runs on an adapter that never got a request token
$adapter = $provider->getAdapter();
$adapter->finishOAuthHandshake();

// after: restore the request token captured during the authenticate phase
$adapter = $provider->getAdapter();
$adapter->setToken($stored_request_token);
$adapter->finishOAuthHandshake();
Defensive patterns

Strategy: validation

Validate before calling

// Restore request-token state before finishing the handshake
if (!strlen($adapter->getToken())) {
  $stored_token = $auth_workflow_state->getRequestToken();
  if (!strlen($stored_token)) {
    return $this->restartAuthenticateFlow();
  }
  $adapter->setToken($stored_token);
}

Try / catch

try {
  $adapter->finishOAuthHandshake();
} catch (Exception $ex) {
  // Token state was lost; send the user back through getAuthenticateURI().
  return $this->restartAuthenticateFlow();
}

Prevention

When it happens

Trigger: Calling finishOAuthHandshake() (directly or through an adapter's getAccountData()/login path) on a freshly constructed adapter whose setToken() was never called or was called with an empty value; a controller that reaches the callback branch of the flow before getAuthenticateURI() ever ran; state loss between the redirect request and the callback request because adapters are rebuilt per request and the token was not persisted/restored.

Common situations: Custom login controller invoking the callback logic directly; a broken flow order after refactoring; the oauth_token query parameter from the provider's redirect being dropped before it is fed back into the adapter; session/state storage silently failing.

Understand the failure class

Related errors


AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21). Data as JSON: /api/errors/3bb29f06e2e16389. Report an issue: GitHub.