phacility/phabricator · error · Exception

Duo API credential ("%s") has no secret key.

Error message

Duo API credential ("%s") has no secret key.

What it means

The Passphrase credential referenced by the Duo provider exists, but $credential->getSecret() returns empty: the credential has no usable secret (never set, cleared, or destroyed). Duo's signed API requests require the secret key (S-key), so newDuoFuture() cannot construct PhabricatorDuoFuture and throws immediately.

Source

Thrown at src/applications/auth/factor/PhabricatorDuoAuthFactor.php:742

    $omnipotent = PhabricatorUser::getOmnipotentUser();

    $credential = id(new PassphraseCredentialQuery())
      ->setViewer($omnipotent)
      ->withPHIDs(array($credential_phid))
      ->needSecrets(true)
      ->executeOne();
    if (!$credential) {
      throw new Exception(
        pht(
          'Unable to load Duo API credential ("%s").',
          $credential_phid));
    }

    $duo_key = $credential->getUsername();
    $duo_secret = $credential->getSecret();
    if (!$duo_secret) {
      throw new Exception(
        pht(
          'Duo API credential ("%s") has no secret key.',
          $credential_phid));
    }

    $duo_host = $provider->getAuthFactorProviderProperty(
      self::PROP_HOSTNAME);
    self::requireDuoAPIHostname($duo_host);

    return id(new PhabricatorDuoFuture())
      ->setIntegrationKey($duo_key)
      ->setSecretKey($duo_secret)
      ->setAPIHostname($duo_host)
      ->setTimeout(10)
      ->setHTTPMethod('POST');
  }

  private function getDuoUsername(

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Edit the Passphrase credential and set its secret to the Duo S-key.
  2. If the credential is destroyed, create a new credential with both the integration key (username) and secret key, then re-select it on the provider.
  3. Confirm the credential is a password-type credential as expected by the provider form.
  4. Test by issuing a Duo challenge after saving.
Defensive patterns

Strategy: validation

Validate before calling

// Run when saving the provider config.
$credential = id(new PassphraseCredentialQuery())
  ->setViewer(PhabricatorUser::getOmnipotentUser())
  ->withPHIDs(array($phid))
  ->needSecrets(true)
  ->executeOne();
$secret = $credential ? $credential->getSecret() : null;
if (!$secret || !strlen($secret->openEnvelope())) {
  // reject the save: Duo requires integration key AND secret key
}

Prevention

When it happens

Trigger: Creating the provider's credential without a secret value, destroying the secret while keeping the credential row, or pointing the provider at a credential of the wrong shape. Any Duo API call for that provider then throws before any network traffic.

Common situations: Admin creates the credential with only the integration key and skips the secret; rotates keys and blanks the old secret; destroys the credential in Passphere/Passphrase UI but leaves the provider pointing at it.

Related errors


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