Leantime/leantime · error · RuntimeException

sprintf($this->language->__($translationKey), ...$values)

Error message

sprintf($this->language->__($translationKey), ...$values)

What it means

displayError() (Oidc.php:640-644) is the single exit point for every translated OIDC login failure: it resolves the key via $this->language->__($translationKey) and throws RuntimeException(sprintf(...)) with the interpolated message. Known call sites: oidc.error.providerMismatch (issuer 'iss' does not equal the configured provider URL), oidc.error.unsupportedAlgorythm (only RS256 is mapped), oidc.error.unsupportedKeyFormat (JWKS key has neither x5c nor n/e). A second failure mode exists: sprintf() itself throws a PHP ValueError when the translation string contains more %-specifiers (or invalid ones) than values passed, which happens with hand-edited or partial language INI overrides of oidc.error.* keys.

Source

Thrown at app/Domain/Oidc/Services/Oidc.php:643

    {
        $storedState = (string) session('oidc.state');
        session()->forget('oidc.state');

        return $storedState !== '' && hash_equals($storedState, $state);
    }

    private function decodeBase64Url(string $value): string
    {
        return base64_decode(strtr($value, '-_', '+/'));
    }

    /**
     * @throws HttpResponseException
     */
    private function displayError(string $translationKey, string ...$values): void
    {

        throw new \RuntimeException(sprintf($this->language->__($translationKey), ...$values));
    }
}

View on GitHub (pinned to 9a9f49f100)

Solutions

  1. Compare the token's iss claim byte-for-byte with the configured provider URL and align LEAN_OIDC_PROVIDER_URL (trailing slash is already tolerated).
  2. For unsupportedAlgorythm: switch the IdP client/realm signing algorithm to RS256 - getAlgorythm() only maps RS256 to OPENSSL_ALGO_SHA256.
  3. For unsupportedKeyFormat: point jwks_uri at an endpoint exposing x5c or n/e entries (for Google use https://www.googleapis.com/oauth2/v1/certs) or pin a certificate via env.
  4. For sprintf ValueError: fix the language INI override so the placeholder count matches the call (providerMismatch and unsupportedAlgorythm take exactly one/two %s).
  5. Check the raw translated string: php artisan tinker >>> app()->make(Language::class)->__('oidc.error.providerMismatch') to see exactly what sprintf receives.

Example fix

; before (custom de-DE.ini override - sprintf() gets 0 placeholders but 2 values)
oidc.error.providerMismatch="Der Provider stimmt nicht mit der lokalen Einstellung überein"

; after - exactly two %s, matching displayError('oidc.error.providerMismatch', $iss, $providerUrl)
oidc.error.providerMismatch="Der zurückgegebene Provider %s stimmt nicht mit der lokalen Einstellung %s überein"
Defensive patterns

Strategy: validation

Validate before calling

// before translating/interpolating, ensure the translation keeps one %s per value
$translated = $this->language->__($translationKey);
if (substr_count($translated, '%') !== count($values)) {
    Log::warning("OIDC translation placeholder mismatch for {$translationKey}");
    $translated = implode(' ', array_merge([$translationKey], $values)); // safe, specifier-free fallback
}
throw new \RuntimeException(sprintf($translated, ...$values));

Type guard

/** True when a translation string's specifier count matches the values about to be passed. */
function translationPlaceholdersMatch(string $translated, int $valueCount): bool
{
    return substr_count($translated, '%s') === $valueCount
        && substr_count($translated, '%') === $valueCount; // no stray % or exotic specifiers
}

Try / catch

try {
    $oidc->login();
} catch (\RuntimeException $e) {
    // intended OIDC validation failures arrive here, already localized
    return redirect('/login')->withErrors($e->getMessage());
} catch (\ValueError $e) {
    // sprintf() itself failed: a language INI override has mismatched %s placeholders
    Log::error('OIDC translation defect: '.$e->getMessage());
    return redirect('/login')->withErrors('Login provider misconfigured. Contact the admin.');
}

Prevention

When it happens

Trigger: Any displayError() call during /oidc/callback: (1) the id_token's iss claim differs from LEAN_OIDC_PROVIDER_URL (only trailing slashes are trimmed); (2) token alg is ES256/HS256 instead of RS256; (3) JWKS keys carry neither x5c nor n/e; (4) sprintf ValueError when a custom language INI defines e.g. oidc.error.providerMismatch without exactly two %s placeholders.

Common situations: Trailing path or http/https mismatch between the configured provider URL and the issuer claim (e.g. 'https://sso.example.com/realms/x' vs 'https://sso.example.com/realms/x/'); provider signing with ES256 (Keycloak default for newer realms is RS256 but clients sometimes switch); untranslated or custom language packs whose oidc.error.* strings lost their %s placeholders; provider responding at a slightly different issuer after an upgrade.

Related errors


AI-assisted analysis of Leantime/leantime@9a9f49f100 (2026-08-21). Data as JSON: /api/errors/70f047fb04e77ac7. Report an issue: GitHub.