Proposal: Keycloak SPI extension for native ID-JAG issuance #18
Replies: 3 comments
|
So quick clarification - this proposed extension is for demo purposes only right? |
|
Understood now - tthat this would be a contribution to Keycloak |
|
Adding this 3rd option from slack from @jadiaconu - "Present the idjag-issuer as a Brokered IdP and its role will be to broker auth delegations but also apply some policy". Jean's elaboration is further added here - "Sure, the idea was that if we don’t want to add a Keycloak extension, which in my opinion is good for a demo but incomplete as a full XAA implementation, we could come with another Idp that sits in the middle and will do a token exchange XAA transition. But if it is meant only for this, the story is weak, why would you have another IdP just for this reason. So basically, applying policies (user/scopes) would give it a good meaning." My thinking around this are - a Brokered-id-gw(Envoy AI gateway) implementation that functions as a trusted " broker" that talks to any number of IDPs, which has a trust chain built using root from open source providers (LetsEncrypt) or Digicert ? The brokered-id-gw(aka authorising proxy) implements XAA token exchange and does policy using OPA/Cedar and active policy also as an add-on in the same GW but a different component. @sriaradhyula @gbshankar @AKRita23 @sevansdell. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
This proposal originates from Jean DIACONU's feedback on Slack after reviewing the live demo. Quoting the relevant part:
The observation is correct.
idjag-issueris the one component in this demo where the implementation diverges most from what a production architecture would look like. Keycloak 26.7 shipsidentity-assertion-jwt, which allows it to validate an ID-JAG-shaped assertion via thejwt-bearergrant, but Keycloak has no corresponding capability to issue one.idjag-issuercloses that gap today as a standalone FastAPI service holding its own RSA keypair, independent of Keycloak's own key material. This works, but it means the cross-domain trust chain roots in a mock service rather than in Keycloak itself, which is a legitimate architectural weakness worth addressing directly.Current flow
sequenceDiagram participant OC as OpenCode<br/>(Org A agent) participant IDJ as idjag-issuer<br/>(separate mock service,<br/>own throwaway keypair) participant EnvoyA as Envoy A<br/>(org-a egress) participant KCB as Keycloak B<br/>(org-b realm) OC->>IDJ: POST /mint<br/>sub, aud=KC-B issuer, act_chain, scope, intent IDJ->>IDJ: sign with its own RSA keypair<br/>(not Keycloak's) IDJ-->>OC: signed ID-JAG assertion OC->>EnvoyA: egress check (assertion) EnvoyA->>IDJ: fetch JWKS — separate trust anchor<br/>from anything Keycloak already publishes EnvoyA-->>OC: ALLOW OC->>KCB: grant_type=jwt-bearer, assertion KCB->>IDJ: validate issuer/JWKS<br/>(identity-assertion-jwt feature) KCB-->>OC: access_token (triage:create)Under this design, Envoy and Keycloak B each independently establish trust in
idjag-issuer's JWKS. Two separate parties rely on one bespoke trust anchor that has no relationship to Keycloak's own keys.Proposed flow
sequenceDiagram participant OC as OpenCode<br/>(Org A agent) participant KCA as Keycloak A<br/>(org-a realm +<br/>ID-JAG SPI extension) participant EnvoyA as Envoy A<br/>(org-a egress) participant KCB as Keycloak B<br/>(org-b realm) OC->>KCA: grant_type=token-exchange<br/>requested_token_type=...id-jag<br/>subject_token=Sarah, actor_token=badge KCA->>KCA: custom SPI mints ID-JAG,<br/>signs with the realm's OWN active key KCA-->>OC: signed ID-JAG assertion OC->>EnvoyA: egress check (assertion) EnvoyA->>KCA: fetch /realms/org-a/protocol/openid-connect/certs<br/>(Keycloak's own, already-published JWKS) EnvoyA-->>OC: ALLOW OC->>KCB: grant_type=jwt-bearer, assertion KCB->>KCA: validate against org-a's real<br/>OIDC discovery/JWKS KCB-->>OC: access_token (triage:create)Keycloak A signs the assertion with its own active realm key rather than a throwaway one, and both Envoy and Keycloak B come to trust the same JWKS Keycloak already publishes. This is a substantive architectural improvement, not a relabeling of the existing mock.
Integration point within Keycloak
Keycloak's SPI model is additive by design: a JAR placed under
/opt/keycloak/providers/and registered viakc.sh build, with no changes required to core Keycloak code.flowchart TB subgraph KC["Keycloak A (org-a realm)"] TE["Token endpoint<br/>/protocol/openid-connect/token"] subgraph SPI["Custom SPI extension (this proposal)"] TEP["TokenExchangeProvider<br/>handles requested_token_type = id-jag"] RRP["(alternative) RealmResourceProvider<br/>/realms/org-a/idjag/mint"] AUTHZ["Delegation authorization logic:<br/>which client may assert which subject,<br/>to which audience, with which scope"] SIGN["Sign with the realm's active key<br/>session.keys().getActiveKey(realm, SIG, RS256)"] end Certs["/protocol/openid-connect/certs<br/>(existing JWKS — unchanged)"] end TE --> TEP TEP --> AUTHZ --> SIGN --> Certs RRP -.alternative integration path.-> AUTHZTwo candidate approaches
A — Custom
TokenExchangeProvider(recommended). ID-JAG minting becomes a standardgrant_type=urn:ietf:params:oauth:grant-type:token-exchangecall with a customrequested_token_type. This matches how Okta's own ID-JAG proposal is structured, and aligns directly with "following Okta's definition." It is the more standards-idiomatic path.B — Custom
RealmResourceProviderFactory. A dedicated endpoint, e.g./realms/org-a/idjag/mint, entirely separate from Keycloak's token-endpoint handling. Lower integration risk, since it touches less of Keycloak's core request path, but functionally this is closer to relocatingidjag-issuerinside Keycloak than to giving Keycloak a native capability.Where the real effort lies
The signing and JWKS-publication work is largely mechanical. The substantial engineering effort is in the delegation-authorization logic:
idjag-issuer's current implementation decides, inline, whether a given caller is authorized to assert delegation for a given subject, to a given audience, at a given scope. That logic must be reimplemented in Java as SPI code, and it does not port over as a simple translation — Keycloak has no built-in model for "this client may vouch for that subject to a different realm." This is the actual substance of the gap Jean identified; everything else in this proposal is comparatively routine implementation work.Downstream impact if adopted
keycloak-amoves off the stockkeycloak:26.7image to a custom build including the provider JAR.envoy-org-a's hardcoded JWKS URI for the actor token changes fromidjag-issuer:9000/jwksto Keycloak A's own certs endpoint.webappandopencode-agent's_mint_idjag()call changes from a REST POST to a token-exchange call.idjag-issueris retired entirely, or retained to demonstrate the case where the issuing party is not Keycloak.Open questions
draft-ietf-oauth-identity-assertion-authz-grant)? These are not guaranteed to be identical.idjag-issuerretire, or remain as the non-Keycloak-issuer interop case?This is a proposal intended to prompt discussion, not a committed plan. No implementation work has started. It represents genuine, non-trivial Keycloak SPI development in Java, and is better scoped as a dedicated follow-up effort than folded into ongoing work.
All reactions