diff --git a/docs/enhancements/id-jag-2026-03-02#4600.md b/docs/enhancements/id-jag-2026-03-02#4600.md index 81ac1de0..af2bc7e3 100644 --- a/docs/enhancements/id-jag-2026-03-02#4600.md +++ b/docs/enhancements/id-jag-2026-03-02#4600.md @@ -123,8 +123,10 @@ sequenceDiagram Clients can request ID-JAG tokens from Dex's `/token` endpoint by specifying `requested_token_type=urn:ietf:params:oauth:token-type:id-jag` in a -Token Exchange request. ID-JAG support is gated behind a feature flag -(`enableIDJAG`) and disabled by default. +Token Exchange request. ID-JAG support is enabled by adding +`urn:ietf:params:oauth:token-type:id-jag` to `oauth2.tokenExchange.tokenTypes`. +When not listed, requests with this `requested_token_type` are rejected, +ensuring no change in behavior for existing deployments. The request parameters (extending existing Token Exchange): @@ -152,25 +154,58 @@ The response: - `issued_token_type`: `urn:ietf:params:oauth:token-type:id-jag` - `token_type`: `N_A` (this is not an OAuth access token) - `expires_in`: lifetime in seconds (default: 300, configurable independently of ID token - lifetime via `idJAGTokensValidFor`) + lifetime via `expiry.idJAGTokens`) - `scope`: OPTIONAL if the issued scope is identical to the requested scope; REQUIRED otherwise. Per Section 4.3.2 of the specification, policy evaluation at the IdP may result in different scopes being issued than were requested. -ID-JAG policy configuration: +Complete configuration example: ```yaml -tokenExchange: - enableIDJAG: true # default: false; feature flag for ID-JAG support - idJAGTokensValidFor: "5m" # default: 5m; independent of idTokensValidFor - idJAGPolicies: - - clientID: "wiki-app" +oauth2: + grantTypes: + - authorization_code + - urn:ietf:params:oauth:grant-type:token-exchange + tokenExchange: + # List of token types enabled for exchange. Adding id-jag enables ID-JAG support. + # Omitting it (default) disables ID-JAG without affecting other token exchange flows. + # SAML2 (urn:ietf:params:oauth:token-type:saml2) may be added in a future release. + tokenTypes: + - urn:ietf:params:oauth:token-type:id_token + - urn:ietf:params:oauth:token-type:id-jag + +expiry: + idTokens: "24h" + idJAGTokens: "5m" # default: 5m; independent of idTokens + +staticClients: + - id: wiki-app + name: "Wiki Application" + secret: "wiki-secret" + redirectURIs: + - "https://wiki.example/callback" + # Per-client ID-JAG policy. Clients without this section cannot obtain ID-JAG tokens + # (default-deny). Only audiences and scopes listed here may be requested. + idJAGPolicies: allowedAudiences: - "https://chat.example/" - "https://calendar.example/" allowedScopes: - "chat.read" - "calendar.read" + + - id: supermarket-app + name: "Supermarket Application" + secret: "supermarket-secret" + redirectURIs: + - "https://supermarket.example/callback" + idJAGPolicies: + allowedAudiences: + - "https://grocery.store.1/" + - "https://grocery.store.2/" + allowedScopes: + - "eat.bananas" + - "eat.apples" ``` ### Implementation Details/Notes/Constraints @@ -179,14 +214,18 @@ tokenExchange: per Section 3 of the specification (header `typ: "oauth-id-jag+jwt"`, claims including `iss`, `sub`, `aud`, `client_id`, `jti`, `exp`, `iat`). -- A new config-based policy evaluator controls which clients can request ID-JAGs for which - audiences, with a default-deny posture when policies are configured. +- Per-client `idJAGPolicies` in `staticClients` control which audiences and scopes a + given client may request in an ID-JAG. Clients without `idJAGPolicies` are denied + by default. Dynamically registered clients are currently unsupported for ID-JAG policies; + support via DB-stored policies or CEL expressions (#4601) is deferred to a future release. -- OIDC discovery is extended with `id_jag_signing_alg_values_supported` per Section 6. +- OIDC discovery is extended with `identity_chaining_requested_token_types_supported` per + Section 6 of the specification. When ID-JAG is enabled, Dex includes + `urn:ietf:params:oauth:token-type:id-jag` in this metadata property. -- ID-JAG support is gated behind the `enableIDJAG` feature flag. When disabled (default), - requests with `requested_token_type=urn:ietf:params:oauth:token-type:id-jag` are - rejected, ensuring no change in behavior for existing deployments. +- ID-JAG support is enabled by listing `urn:ietf:params:oauth:token-type:id-jag` in + `oauth2.tokenExchange.tokenTypes`. When not listed (default), requests are rejected, + ensuring no change in behavior for existing deployments. ### Risks and Mitigations @@ -198,11 +237,14 @@ tokenExchange: - **Replay attack risk**: Server-side `jti` tracking is deferred, so a stolen ID-JAG can be replayed within its 5-minute lifetime. Short `expires_in` is the only Dex-side mitigation; Resource Authorization Servers should implement `jti` caching independently. +- **Public client misuse**: Per Section 7.1 of the specification, ID-JAG SHOULD only be + used by confidential clients. Public clients should use the standard authorization code + flow with interactive user consent at the Resource Authorization Server. Dex will enforce + this by rejecting ID-JAG requests from public clients (clients without a secret). - **Breaking changes**: None. This is purely additive to the existing Token Exchange implementation. The `audience` parameter is newly introduced (not previously used in the implementation despite DEP #2812's original proposal), - and `connector_id` already exists. The feature flag ensures existing deployments - are unaffected. + and `connector_id` already exists. ### Alternatives