Signing keys
Every tenant gets an RSA keypair when it is created. The private half is encrypted at rest; the public half is published as a JWK through that tenant’s JWKS endpoint and no other.
GET https://jons.auth.example/.well-known/jwks.json{"keys":[{"kty":"RSA","use":"sig","alg":"RS256","kid":"e9914114-…","n":"…","e":"AQAB"}]}Why this is the layer worth having
Section titled “Why this is the layer worth having”Application scopes fail by omission — someone forgets one and rows leak. Row-level security fails silently if the connecting role happens to be a superuser. Both are real defences and both fail quietly.
A per-tenant signing key cannot fail quietly. Present one tenant with another’s token and the rejection is not a policy decision at all:
{"error":"invalid_token", "error_description":"Could not find public key for kid e9914114-08c3-438b-a4f4-d71b70489bde"}There is no code path where the wrong tenant considers the token and decides against it. The key that signed it was never published where they could look, so the token is unreadable rather than unauthorized.
Rotation
Section titled “Rotation”SigningKey.rotate! mints a replacement, activates it immediately, and marks the outgoing key
retired in the future rather than now:
SigningKey.rotate!(tenant: tenant)The outgoing key keeps being published for an overlap window (24 hours by default), so tokens signed
just before the rotation still verify while nothing new is signed with it. SigningKey.published
is the JWKS scope and includes both; SigningKey.active is what signing uses and includes only the
new one.
