BookStackApp/BookStack · error · OidcInvalidKeyException

A "n" parameter on the provided key is expected

Error message

A "n" parameter on the provided key is expected

What it means

Validation guard in OidcJwtSigningKey::loadFromJwkArray that rejects a JWK missing the RSA modulus parameter 'n'. Fires when the identity provider's discovery keys omit 'n', making the key incomplete and unusable for RS256 signature verification.

Solutions

  1. Include the full base64url-encoded modulus 'n' in the JWK
  2. Re-fetch the complete JWK from the IdP's jwks_uri
  3. Check that config/DB storage is not truncating the long 'n' value (column size, YAML line wrapping)
  4. For fixtures, generate a real keypair (openssl genrsa) and embed the actual n

Example fix

// before
['kty' => 'RSA', 'e' => 'AQAB', 'use' => 'sig']
// after
['kty' => 'RSA', 'e' => 'AQAB', 'n' => '<base64url modulus from jwks_uri>', 'use' => 'sig']
Defensive patterns

Strategy: validation

Validate before calling

if (empty($jwk['n'])) { throw new \RuntimeException('JWK missing n parameter'); }

Type guard

function hasRsaPublicParams(array $jwk): bool { return !empty($jwk['e']) && !empty($jwk['n']); }

Try / catch

try { $key = new OidcJwtSigningKey($jwk); } catch (OidcInvalidKeyException $e) { if (str_contains($e->getMessage(), '"n" parameter')) { /* refetch full JWK / fix storage truncation */ } throw $e; }

Prevention

When it happens

Trigger: new OidcJwtSigningKey($jwkArray) with a JWK missing 'n' or with an empty/null 'n' (after kty/use/e checks passed).

Common situations: Incomplete hand-written JWK fixtures; base64url payload stripped by config serialization; only 'kid'/'kty' copied from discovery; upstream JWKS bug omitting the modulus.

Related errors


AI-assisted analysis of BookStackApp/BookStack@18f8469a1c (2026-09-02). Data as JSON: /api/errors/9959f9198dc3b9d4. Report an issue: GitHub.

Appendix: source

Thrown at app/Access/Oidc/OidcJwtSigningKey.php:77

        // it exists otherwise presume it will be compatible.
        $alg = $jwk['alg'] ?? null;
        if ($jwk['kty'] !== 'RSA' || !(is_null($alg) || $alg === 'RS256')) {
            throw new OidcInvalidKeyException("Only RS256 keys are currently supported. Found key using {$alg}");
        }

        // 'use' is optional for a JWK but we assume 'sig' where no value exists since that's what
        // the OIDC discovery spec infers since 'sig' MUST be set if encryption keys come into play.
        $use = $jwk['use'] ?? 'sig';
        if ($use !== 'sig') {
            throw new OidcInvalidKeyException("Only signature keys are currently supported. Found key for use {$jwk['use']}");
        }

        if (empty($jwk['e'])) {
            throw new OidcInvalidKeyException('An "e" parameter on the provided key is expected');
        }

        if (empty($jwk['n'])) {
            throw new OidcInvalidKeyException('A "n" parameter on the provided key is expected');
        }

        $n = strtr($jwk['n'], '-_', '+/');

        try {
            $key = PublicKeyLoader::load([
                'e' => new BigInteger(base64_decode($jwk['e']), 256),
                'n' => new BigInteger(base64_decode($n), 256),
            ]);
        } catch (\Exception $exception) {
            throw new OidcInvalidKeyException("Failed to load key from JWK parameters with error: {$exception->getMessage()}");
        }

        if (!$key instanceof RSA) {
            throw new OidcInvalidKeyException('Key loaded from file path is not an RSA key as expected');
        }

        $this->key = $key->withPadding(RSA::SIGNATURE_PKCS1);

View on GitHub (pinned to 18f8469a1c)