passbolt/passbolt_api · error · InternalErrorException

An authentication token state is required.

Error message

An authentication token state is required.

What it means

After consuming the token, assertDuoStateMatchesAuthenticationTokenState reads the token's custom 'state' payload via getDataValue('state'). Duo's OAuth-style flow requires a state value stored when the token was issued. If the stored state is empty, the server raises InternalErrorException because this indicates the token was created without the required state data — an internal invariant breach, not a user fault.

Solutions

  1. Regenerate the token via the proper Duo setup/verify start service so the state is stored in the token data
  2. Inspect the authentication_tokens.data column for the token and confirm it contains JSON with a non-empty state
  3. Ensure the token-issuing service passes the duo state into the token creation data payload
  4. Clear stale pre-upgrade tokens (e.g. via the MFA settings reset) and restart the flow

Example fix

// before (issuing code)
$token = $authenticationTokens->generate($userId, AuthenticationToken::TYPE_MFA_SETUP);
// after
$token = $authenticationTokens->generate(
    $userId,
    AuthenticationToken::TYPE_MFA_SETUP,
    ['state' => $duoState]
);
Defensive patterns

Strategy: validation

Validate before calling

$data = json_decode($tokenEntity->data ?? '', true) ?? [];
if (empty($data['state'])) {
    throw new \RuntimeException('Token issued without Duo state; regenerate it.');
}

Try / catch

try {
    $authToken = $service->consumeAndVerifyAuthenticationToken($uac, $type, $token, $state);
} catch (\Cake\Http\Exception\InternalErrorException $e) {
    $this->log('Token missing Duo state: ' . $e->getMessage());
    // regenerate the token through the proper Duo start service
}

Prevention

When it happens

Trigger: A Duo callback authentication token exists and consumes fine, but its data payload has no 'state' key or an empty value — typically because the token was created by code that did not set the state data, or the data column was truncated/lost.

Common situations: Tokens created before the Duo state feature was introduced still sitting in the table; a custom or older token-generation path that omits the data field; manual database inserts during testing without the data payload.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17). Data as JSON: /api/errors/a6088abb88c678aa. Report an issue: GitHub.

Appendix: source

Thrown at plugins/PassboltCe/MultiFactorAuthentication/src/Service/Duo/MfaDuoCallbackAuthenticationTokenService.php:116

        }
    }

    /**
     * Assert the Duo callback authentication token state value.
     *
     * @param \App\Model\Entity\AuthenticationToken $authToken The callback authentication token
     * @param string $duoState The Duo callback state
     * @return void
     * @throws \Cake\Http\Exception\InternalErrorException if the callback authentication token does not have state defined
     * @throws \Cake\Http\Exception\UnauthorizedException if the callback authentication token state value does not match the Duo callback state
     */
    private function assertDuoStateMatchesAuthenticationTokenState(
        AuthenticationToken $authToken,
        string $duoState
    ): void {
        $authTokenState = $authToken->getDataValue('state');
        if (empty($authTokenState)) {
            throw new InternalErrorException(__('An authentication token state is required.'));
        }
        if ($authTokenState !== $duoState) {
            throw new UnauthorizedException(__('The Duo state should match the authentication token state.'));
        }
    }
}

View on GitHub (pinned to 31c1bbc10f)