passbolt/passbolt_api · error · InternalErrorException

Invalid response. Missing token endpoint.

Error message

Invalid response. Missing token endpoint.

What it means

validateOpenIdConfiguration() requires token_endpoint in the discovery document because passbolt exchanges the authorization code for tokens there (getBaseAccessTokenUrl). Its absence indicates an incomplete OIDC discovery payload, so an InternalErrorException is thrown.

Solutions

  1. Curl the discovery URL and verify token_endpoint is present.
  2. Fix the issuer/WellKnownURI configured in passbolt SSO settings.
  3. Purge intermediary caches/proxies serving stale discovery metadata.
  4. Reconfigure the IdP to publish compliant discovery metadata.

Example fix

// before
'{"jwks_uri":"https://auth.example.com/jwks"}'
// after
'{"token_endpoint":"https://auth.example.com/token","jwks_uri":"https://auth.example.com/jwks"}'
Defensive patterns

Strategy: validation

Validate before calling

$doc = json_decode(file_get_contents($wellKnownUrl), true);
if (!isset($doc['token_endpoint'])) { throw new UnexpectedValueException('Discovery document missing token_endpoint.'); }

Type guard

function hasTokenEndpoint(mixed $doc): bool { return is_array($doc) && isset($doc['token_endpoint']) && is_string($doc['token_endpoint']); }

Try / catch

try { $tokenUrl = $provider->getBaseAccessTokenUrl(); } catch (InternalErrorException $e) { if (str_contains($e->getMessage(), 'token endpoint')) { /* incomplete discovery metadata */ } throw $e; }

Prevention

When it happens

Trigger: getBaseAccessTokenUrl -> getOpenIdConfiguration -> validateOpenIdConfiguration with decoded JSON missing the token_endpoint key.

Common situations: IdP publishing partial metadata; discovery URL resolving to a different app's document; proxy/cache serving stale metadata; misconfigured multi-tenant issuer.

Related errors


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

Appendix: source

Thrown at plugins/PassboltEe/Sso/src/Utility/Provider/AbstractOauth2Provider.php:181

    {
        if (!is_array($response)) {
            $msg = sprintf('Invalid response. Expected array, got "%s".', gettype($response));
            if (is_string($response)) {
                // Cap excerpt to limit log volume on large/HTML responses; mb_strcut is UTF-8-safe.
                $excerpt = mb_strcut($response, 0, 200, 'UTF-8');
                // Escape newlines and control characters via JSON encoding so they don't corrupt log output.
                $msg .= ' ' . sprintf('Response text (truncated): %s', json_encode($excerpt));
            }
            throw new InternalErrorException($msg);
        }
        if (!isset($response['jwks_uri'])) {
            throw new InternalErrorException('Invalid response. Missing JWKS URI');
        }
        if (!isset($response['authorization_endpoint'])) {
            throw new InternalErrorException('Invalid response. Missing authorization endpoint.');
        }
        if (!isset($response['token_endpoint'])) {
            throw new InternalErrorException('Invalid response. Missing token endpoint.');
        }
        if (!Validation::url($response['jwks_uri'])) {
            throw new InternalErrorException('Invalid response. Invalid JWKS URI');
        }
        if (!Validation::url($response['authorization_endpoint'])) {
            throw new InternalErrorException('Invalid response. Invalid authorization endpoint.');
        }
        if (!Validation::url($response['token_endpoint'])) {
            throw new InternalErrorException('Invalid response. Invalid token endpoint.');
        }
    }

    /**
     * @inheritDoc
     */
    protected function getAuthorizationParameters(array $options)
    {
        $options = parent::getAuthorizationParameters($options);

View on GitHub (pinned to 31c1bbc10f)