passbolt/passbolt_api · error · BadRequestException

$e->getMessage() from OAuth2Exception during admin SSO…

Error message

$e->getMessage() from OAuth2Exception during admin SSO setup (dynamic), remapped to 400

What it means

During admin SSO setup stage2, assertStateCodeAndGetUac() exchanges the OAuth2 code with the provider and validates the state code. Any OAuth2Exception (token exchange failure, invalid code, wrong redirect_uri, provider error response) is remapped from a 500-level OAuth2Exception to a 400 BadRequestException carrying its dynamic message, with the original chained.

Solutions

  1. Re-read the message in the 400 response — it names the provider-side cause (e.g. invalid_grant, redirect_uri_mismatch) and fix the corresponding SSO draft setting.
  2. Restart the SSO setup from the admin settings screen to get a fresh authorization code; codes are single-use and expire in minutes.
  3. Verify client id, client secret, and redirect URI in the draft exactly match the provider's app registration.
  4. Check server outbound connectivity to the provider's token endpoint (firewall/proxy).
Defensive patterns

Strategy: try-catch

Validate before calling

if (!clientId || !clientSecret || !redirectUri) { throw new Error('SSO provider config incomplete before starting OAuth flow'); }

Type guard

function isOAuth2Exception(e) { return e && (e.name === 'OAuth2Exception' || /^OAuth2/.test(String(e.class))); }

Try / catch

try { await stage2AsAdmin(state, code); } catch (e) { if (e.status === 400 && /invalid_grant|redirect_uri_mismatch|invalid_client/.test(e.message)) { fixProviderSettings(e.message); restartSetup(); } else { throw e; } }

Prevention

When it happens

Trigger: Admin finishes the OAuth2 redirect and the controller calls assertStateCodeAndGetUac(); the provider rejects the code — expired/already-used authorization code, mismatched client_secret, wrong redirect URI, or misconfigured provider endpoints.

Common situations: Wrong client secret or redirect URI in the SSO draft settings; provider (Azure AD, Google, etc.) clock skew invalidating codes; user replaying the callback URL (code is single-use); network/DNS issues reaching the provider's token endpoint during setup.

Related errors


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

Appendix: source

Thrown at plugins/PassboltEe/Sso/src/Controller/AbstractSso2Stage2Controller.php:225

     * @param \App\Service\Cookie\AbstractSecureCookieService $cookieService Cookie service
     * @param \Passbolt\Sso\Model\Entity\SsoState $ssoState SSO state.
     * @param string $code jwt
     * @return void
     */
    protected function stage2AsAdmin(AbstractSecureCookieService $cookieService, SsoState $ssoState, string $code): void
    {
        try {
            // Get the draft settings
            $settingsDto = (new SsoSettingsGetService())->getDraftByIdOrFail($ssoState->sso_settings_id, true);
        } catch (Exception $exception) {
            throw new BadRequestException($exception->getMessage(), 400, $exception);
        }

        try {
            $service = $this->ssoServiceFactory($cookieService, $settingsDto);
            $uac = $service->assertStateCodeAndGetUac($ssoState, $code, $this->User->ip(), $this->User->userAgent());
        } catch (OAuth2Exception $e) { // Remap 500 error with 400 when admin is setting up SSO
            throw new BadRequestException($e->getMessage(), 400, $e);
        }

        // Create authentication token for next step, e.g. activate settings
        $ssoAuthToken = $service->createAuthTokenToActiveSettings($uac, $service->getSettings()->id);

        $this->response = $this->getResponse()->withCookie($service->clearStateCookie());
        $this->redirect(Router::url("/sso/login/dry-run/success?token={$ssoAuthToken->token}", true));
    }
}

View on GitHub (pinned to 31c1bbc10f)