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#UserInfoResponseView on GitHub (pinned to 18f8469a1c)
Solutions
- 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'
- 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)
- Confirm the key is fetched from the correct discovery/JWKS URL and that the response is key material, not an HTML error page
- 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
- 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
- Store signing keys as files or secret-manager entries with preserved newlines, not inline env strings
- Pre-parse keys with openssl_pkey_get_public()/phpseclib loaders at startup to fail fast on bad config
- Never commit truncated keys; verify with openssl pkey -pubin after any rotation
- Use the same key-loading helper everywhere so PEM normalization (\n restoration, header/footer) happens once
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
- Missing or non-matching token issuer value
- Could not parse out a valid {$prop} within the provided toke
- Could not parse out a valid signature within the provided to
- Only RS256 signature validation is supported. Token reports
- Token signature could not be validated using the provided ke
AI-assisted analysis of BookStackApp/BookStack@18f8469a1c (2026-09-02).
Data as JSON: /api/errors/82b9a466d4e6341f.
Report an issue: GitHub.