IdentityServer8 was emitting JWTs without a 'kid' header and
serving the JWKS without per-key identifiers, because
LoadSigningCredentialsInner constructed RsaSecurityKey /
ECDsaSecurityKey objects without an explicit KeyId. Resource
servers (Yavsc.Blogs, Yavsc.Api) cannot match a token to a key
in the JWKS without one, so every signature validation failed
with 'The signature key was not found' (Microsoft.IdentityModel
IDX10500).
Root cause: SigningCredentials were built directly from the
BC-parsed key parameters, bypassing the X509Certificate2 path
IdentityServer normally derives the kid from. The fix derives
a stable KeyId from the certificate's SHA-256 thumbprint
(truncated to 16 hex chars) and sets it on both SecurityKey
variants before constructing SigningCredentials.
The thumbprint-based kid is stable across process restarts as
long as the cert doesn't change, and changes naturally on
LetsEncrypt renewal (~90 days), which is the right behaviour:
old tokens age out, resource servers refresh their JWKS cache
to discover the new kid.
Production rollout: redeploy Yavsc.Org and re-login (or let
the refresh-token path rotate) so newly issued tokens carry
the kid. Pre-restart tokens will continue to be rejected with
IDX10500 until they expire or are refreshed.