BookStackApp/BookStack · error · OidcInvalidTokenException

Failed to read signing key with error:

Error message

Failed to read signing key with error: 

What it means

This library validates an OIDC/JWT ID token by parsing each configured signing key into an OidcJwtSigningKey object. If a key string is malformed (not valid PEM/JWK key material), OidcJwtSigningKey's constructor throws OidcInvalidKeyException, which is wrapped and rethrown here as OidcInvalidTokenException with the underlying reason appended to the message.

Source

Thrown at app/Access/Oidc/OidcJwtWithClaims.php:130

        }
    }

    /**
     * Validate the signature of the given token and ensure it validates against the provided key.
     *
     * @throws OidcInvalidTokenException
     */
    protected function validateTokenSignature(): void
    {
        if ($this->header['alg'] !== 'RS256') {
            throw new OidcInvalidTokenException("Only RS256 signature validation is supported. Token reports using {$this->header['alg']}");
        }

        $parsedKeys = array_map(function ($key) {
            try {
                return new OidcJwtSigningKey($key);
            } catch (OidcInvalidKeyException $e) {
                throw new OidcInvalidTokenException('Failed to read signing key with error: ' . $e->getMessage());
            }
        }, $this->keys);

        $contentToSign = $this->tokenParts[0] . '.' . $this->tokenParts[1];
        /** @var OidcJwtSigningKey $parsedKey */
        foreach ($parsedKeys as $parsedKey) {
            if ($parsedKey->verify($contentToSign, $this->signature)) {
                return;
            }
        }

        throw new OidcInvalidTokenException('Token signature could not be validated using the provided keys');
    }

    /**
     * Validate common claims for OIDC JWT tokens.
     * As per https://openid.net/specs/openid-connect-basic-1_0.html#IDTokenValidation
     * and https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse

View on GitHub (pinned to 18f8469a1c)

Solutions

  1. Inspect the message suffix for the underlying OidcInvalidKeyException reason and verify each configured key is complete, valid PEM (-----BEGIN PUBLIC KEY----- ... -----END PUBLIC KEY-----) with real newlines, not literal '\n'
  2. If the key comes from an env var or single-line config, convert it to proper multiline form (e.g. use a secrets file, or replace literal \n with real newlines)
  3. Confirm the key is fetched from the correct discovery/JWKS URL and that the response is key material, not an HTML error page
  4. Verify you are supplying the right key type for the token's alg (e.g. RS256 RSA public key, not EC/Ed25519 or a certificate) and that Base64/dot-separated JWK components are decoded correctly
  5. Regenerate or re-export the key if it is truncated or corrupt: openssl pkey -pubin -in key.pem -text -noout should parse it

Example fix

// before: single-line env value with literal \n
$keys = [getenv('OIDC_PUBLIC_KEY')]; // "-----BEGIN PUBLIC KEY-----\nMIIBI...\n-----END PUBLIC KEY-----" as text
// after: restore real newlines before constructing the validator
$raw = getenv('OIDC_PUBLIC_KEY');
$pem = str_replace('\\n', "\n", $raw);
$keys = [$pem];
Defensive patterns

Strategy: validation

Validate before calling

function isValidPem(string $key): bool {
    $t = str_replace('\\n', "\n", trim($key));
    return str_starts_with($t, '-----BEGIN')
        && str_contains($t, '-----END')
        && openssl_pkey_get_public($t) !== false;
}
$keys = array_values(array_filter($keys, 'isValidPem'));

Try / catch

try {
    $jwt->validateCommonTokenDetails($token, $clientId);
} catch (OidcInvalidTokenException $e) {
    if (str_starts_with($e->getMessage(), 'Failed to read signing key')) {
        $this->logger->error('OIDC signing key unreadable — check key config', ['detail' => $e->getMessage()]);
        throw new ConfigurationException('Invalid OIDC signing key configured');
    }
    throw $e;
}

Prevention

When it happens

Trigger: OidcJwtWithClaims::validateTokenSignature() (called via validateCommonTokenDetails) maps every key in $this->keys through 'new OidcJwtSigningKey($key)'; any key that fails construction throws. Typical causes: truncated PEM (missing header/footer lines), keys stored with escaped or mangled newlines (e.g. env var without \n preserved), a non-key value (HTML, placeholder text) fetched from the JWKS/issuer metadata endpoint, or a key in an unsupported format (DER, SSH format, certificate where a bare public key is expected).

Common situations: OIDC_ID_TOKEN_PUBLIC_KEY / signing-key env var copied from a terminal losing newlines; pasting a private key where a public key is required; a reverse proxy or error page returned where the JWKS was expected; base64-encoded key not decoded before being passed to the library; rotation where the new key was committed corrupted.

Related errors


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