passbolt/passbolt_api · error · InternalErrorException

AccessToken should be an instance of BaseIdToken class.

Error message

AccessToken should be an instance of BaseIdToken class.

What it means

AbstractOauth2Provider::getResourceOwner() is an adapter over the League OAuth2 provider interface, which accepts any AccessTokenInterface. Passbolt's SSO only works with its own BaseIdToken subclass carrying id_token claims, so when a plain League AccessToken is passed it cannot build the resource owner and throws an InternalErrorException.

Solutions

  1. Ensure the provider's token exchange returns a BaseIdToken instance (implement/keep getTokenCredentials() so it wraps the id_token into BaseIdToken)
  2. Check that the subclass of AbstractOauth2Provider does not override getTokenCredentials to return a plain League AccessToken
  3. Verify the id_token (JWT) is actually present in the provider's response; a missing id_token commonly makes the code fall back to a generic token
  4. Reproduce with a unit test passing a League AccessToken to getResourceOwner() to confirm which call site supplies it

Example fix

// before
$token = $provider->getAccessToken('authorization_code', ['code' => $code]); // plain League AccessToken
// after
$token = $provider->getIdToken('authorization_code', ['code' => $code]); // BaseIdToken subclass
$resourceOwner = $provider->getResourceOwner($token);
Defensive patterns

Strategy: type-guard

Validate before calling

if (!$token instanceof \Passbolt\Sso\Utility\BaseIdToken) {
    throw new \InvalidArgumentException('Expected BaseIdToken, got ' . get_class($token));
}

Type guard

function isBaseIdToken($token): bool {
    return $token instanceof \Passbolt\Sso\Utility\BaseIdToken;
}

Try / catch

try {
    $resourceOwner = $provider->getResourceOwner($token);
} catch (\Cake\Http\Exception\InternalErrorException $e) {
    $this->log('SSO token type mismatch: ' . $e->getMessage());
    throw new SsoAuthenticationException('Invalid id_token returned by provider.');
}

Prevention

When it happens

Trigger: Calling getResourceOwner() (directly or via the OAuth2 token-exchange flow) with a token object that is an AccessTokenInterface but not a Passbolt BaseIdToken instance — e.g. a token obtained through the standard League token exchange instead of the SSO-specific code path, or a misconfigured provider returning a generic token class.

Common situations: Custom or overridden OAuth2 provider code constructing the wrong token class; upgrading League/oauth2-client so the base getTokenCredentials() path yields an AccessToken instead of BaseIdToken; subclass overriding createAccessToken/getAccessToken and dropping the id_token handling.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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

Appendix: source

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

    protected function createAccessToken(array $response, AbstractGrant $grant): AccessToken
    {
        return new BaseIdToken($response, $this);
    }

    /**
     * @inheritDoc
     */
    public function getResourceOwner(AccessToken $token): ResourceOwnerInterface
    {
        // We get resource owner information from id_token only
        // We could fall back calling user info API user access_token but we rather not
        if ($token instanceof BaseIdToken) {
            $data = $token->getIdTokenClaims();
            // e.g. token is passed to match League\AbstractProvider interface but not used
            return $this->createResourceOwner($data, $token);
        }

        throw new InternalErrorException('AccessToken should be an instance of BaseIdToken class.');
    }

    /**
     * Get JWT verification keys from Google.
     *
     * @return array
     */
    public function getJwtVerificationKeys(): array
    {
        $openIdConfiguration = $this->getOpenIdConfiguration();
        $keysUri = $openIdConfiguration['jwks_uri'];

        $factory = $this->getRequestFactory();
        $request = $factory->getRequestWithOptions('get', $keysUri, []);

        try {
            $response = $this->getParsedResponse($request);
        } catch (Throwable $exception) {

View on GitHub (pinned to 31c1bbc10f)