fix(auth): jwt kid privatekey mismatch (#104) - #154
Open
BKJN1 wants to merge 1 commit into
Open
Conversation
Contributor
🤖 Augment PR SummarySummary: This PR fixes auth JWTs whose header Changes:
jwt.key-id continue to boot, but signing requests fail until the matching key ID is configured; this avoids producing misleading tokens with invalid signatures.
🤖 Was this summary useful? React with 👍 or 👎 |
| // together (not looked up dynamically from JwksService) so signing can never | ||
| // label a token with a kid that doesn't correspond to the private key that | ||
| // actually signed it. See EdgeConfig.keyId/privateKey for the same pattern. | ||
| keyId: String, |
Contributor
There was a problem hiding this comment.
keyId is now mandatory when parseConfig[CoreConfig] starts auth, but every existing auth config generated before this PR contains only jwt.issuer and jwt.private-key; deploying this image before the separately maintained runtime config is migrated will make auth fail to start.
Severity: high
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
Collaborator
Author
|
augment review |
BKJN1
force-pushed
the
fix/104-jwt-kid-privatekey-mismatch
branch
from
August 8, 2026 10:14
841dc8a to
a80b14f
Compare
Collaborator
Author
|
augment review |
Collaborator
Author
|
@goshacodes |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #104 — auth was labeling JWTs with a
kidfetched dynamically fromJwksServicewhile signing them with a staticconfig.jwt.privateKey. When central rotates the active key, auth picks up the newkidbefore it's redeployed with the matching private key, producing tokens whosekidpoints to a public key that never actually signed them ("ghost key") — verifiers would reject a token that looks correctly keyed but has an invalid signature.Root cause
Same pattern was present in 5 places, not just the 2 listed in the issue:
TokenEndpointController.scala(access token + id token)UserInfoController.scala(JWT userinfo response)LogoutService.scala(back-channel logout token)ConversationRenderService.scala(id_token on conversation complete)AuthorizeEndpointService.scala(id_token, hybrid flow)All five did:
Fix
Added
keyId: StringtoCoreConfig.JwtConfig, sokeyIdandprivateKeyare always the matching pair auth signs with — mirrors the existingEdgeConfig.keyId/privateKeypattern already used for edge's own signing. All 5 sites now sign withconfig.jwt.keyId/JWT.Algorithm.RS256directly, never with whateverJwksServicecurrently reports as "active".JwksServiceis untouched for verification (deserializing inbound tokens still uses the full dynamic key set — that's correct and unrelated to this bug). It's dropped entirely fromLogoutServiceandConversationRenderService, which no longer need it for anything.This doesn't give zero-downtime key rotation (still requires a config/deploy step to stay in sync with central's JWKS) — but it makes drift fail safely: a mismatched
keyIdnow produces an "unknown kid" rejection at verification time instead of a token that looks valid but isn't.Other changes
scripts/gen-env.scala— emitsjwt.key-idalongsidejwt.private-keyin the generated auth conf.Tests
TestEnvConfig.scala—jwtConfignow carrieskeyId.LogoutServiceSpec.scala,ConversationRenderServiceSpec.scala— updated constructor calls for the new (smaller) arity.TokenEndpointControllerSpec.scala— new regression test:JwksServicereports an "active" key with a different, unrelatedkid; asserts/tokenstill signs withconfig.jwt.keyIdand the signature verifies against the real key. Fails on the old code (kid would be the unrelated one), passes now.