phacility/phabricator · error · Exception

Expected verifier to finish OAuth handshake!

Error message

Expected verifier to finish OAuth handshake!

What it means

finishOAuthHandshake() requires an oauth_verifier value (RFC 5849 section 2.2) before it will exchange the request token for an access token. getVerifier() returning empty means the provider's redirect back to your callback did not include (or did not preserve) the verifier parameter.

Source

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

        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;
  }

  private function readTokenAndTokenSecret(array $data) {
    $token = idx($data, 'oauth_token');
    if (!$token) {

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Confirm the provider redirects back with both oauth_token and oauth_verifier in the query string (inspect the callback request URL).
  2. Make sure your code reads oauth_verifier from the request and calls setVerifier() before finishing the handshake.
  3. Fix any redirect/rewrite rules between the provider and your endpoint that discard query parameters.
  4. If the provider only supports OAuth 1.0 (no verifier), it cannot be used with this 1.0a-only adapter.

Example fix

// before: handshake finishes without a verifier
$adapter->finishOAuthHandshake();

// after: pass the verifier from the provider callback through
$adapter->setVerifier($request->getStr('oauth_verifier'));
$adapter->finishOAuthHandshake();
Defensive patterns

Strategy: validation

Validate before calling

// Check the callback parameters before finishing the handshake
$verifier = $request->getStr('oauth_verifier');
if (!strlen($verifier)) {
  return $this->restartAuthenticateFlow();
}
$adapter->setVerifier($verifier);

Try / catch

try {
  $adapter->finishOAuthHandshake();
} catch (Exception $ex) {
  if (strpos($ex->getMessage(), 'verifier') !== false) {
    // Provider redirect lost oauth_verifier; restart the flow.
    return $this->restartAuthenticateFlow();
  }
  throw $ex;
}

Prevention

When it happens

Trigger: The provider redirects to the callback without oauth_verifier (plain OAuth 1.0 behavior or a provider quirk); the callback URI was configured in a way that strips query parameters (e.g., a rewrite rule or a redirect chain that drops the query string); setVerifier() was never called with $request->getStr('oauth_verifier') before the handshake completion; the user hit the callback URL manually.

Common situations: Callback URL configured on the provider that already contains a query string so the verifier gets appended incorrectly or dropped; middleware/redirects normalizing the URL and discarding query params; provider implementing OAuth 1.0 instead of 1.0a; direct navigation/bookmark of the callback URI.

Understand the failure class

Related errors


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