docs/enhancements/id-jag-2026-03-02#4600.md
draft-ietf-oauth-identity-assertion-authz-grant-02 specifies a mechanism for an application to use an identity assertion to obtain an access token for a third-party API by coordinating through a common enterprise identity provider using Token Exchange [RFC 8693] and JWT Profile for OAuth 2.0 Authorization Grants [RFC 7523].
This DEP proposes to extend Dex's existing Token Exchange implementation to support issuing Identity Assertion JWT Authorization Grants (ID-JAGs), enabling cross-domain access managed by the enterprise IdP.
The specification is authored by A. Parecki (Okta), K. McGuinness, and B. Campbell (Ping Identity). It is actively being developed within the IETF OAuth Working Group.
Use cases:
Real-world adoption:
In enterprise environments, applications are configured for SSO through a common IdP. When one application needs to access a user's data at another application, the current approach requires either:
ID-JAG solves this by letting the IdP broker cross-domain access, maintaining visibility and policy control.
Specific goals:
requested_token_type=urn:ietf:params:oauth:token-type:id-jag)audience and resource parameters per the specificationsubject_token_type is deferred.End-to-end flow:
sequenceDiagram
participant C as Client (Wiki App)
participant Dex as Dex (IdP AS)
participant RAS as Resource AS (Chat AS)
participant RS as Resource Server (Chat API)
C->>Dex: 1. OIDC Authentication (authorization_code flow)
Dex-->>C: ID Token + optional Refresh Token
C->>Dex: 2. Token Exchange (grant_type=token-exchange)
subject_token=ID Token
requested_token_type=id-jag
audience=https://acme.chat.example/
connector_id=google
Note over Dex: Validate subject_token aud == client_id
Evaluate policy (clientID → allowed audiences)
Issue ID-JAG JWT (typ: oauth-id-jag+jwt)
Dex-->>C: ID-JAG (access_token, token_type=N_A, expires_in=300)
C->>RAS: 3. JWT Bearer Grant (RFC 7523)
grant_type=jwt-bearer, assertion=ID-JAG
Note over RAS: Validate ID-JAG signature (Dex JWKS)
Validate aud == RAS issuer
Issue access token
RAS-->>C: Access Token
C->>RS: 4. API Request with Access Token
RS-->>C: Protected Resource
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 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):
grant_type: REQUIRED - urn:ietf:params:oauth:grant-type:token-exchangesubject_token: REQUIRED - the identity assertion (OpenID Connect ID Token)subject_token_type: REQUIRED - urn:ietf:params:oauth:token-type:id_token.
SAML 2.0 (urn:ietf:params:oauth:token-type:saml2) is deferred (see Non-goals).requested_token_type: REQUIRED - urn:ietf:params:oauth:token-type:id-jagaudience: REQUIRED - the Issuer URL of the Resource Authorization Server.
Note: The existing Token Exchange implementation uses a Dex-specific connector_id
parameter (not part of RFC 8693) for connector selection. The audience parameter was
not used in the current implementation despite DEP #2812 originally proposing it for
connector identification. ID-JAG introduces audience with its standard RFC 8693
meaning (target Resource AS). This is purely additive and does not affect existing
Token Exchange requests.connector_id: REQUIRED (Dex extension) - the ID of the Dex connector to verify the
subject token against. The connector validates the token (issuer, signature, etc.),
so a mismatched token is rejected. This parameter already exists in the current
Token Exchange implementation and is reused as-is.resource: OPTIONAL - the Resource Identifier of the Resource Serverscope: OPTIONAL - the requested scopes at the Resource ServerThe response:
access_token: the ID-JAG JWT (named access_token for RFC 8693 compatibility)issued_token_type: urn:ietf:params:oauth:token-type:id-jagtoken_type: N_A (this is not an OAuth access token)expires_in: lifetime in seconds (default: 300, configurable independently of ID token
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.Complete configuration example:
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"
A new id-jag branch is added to the existing Token Exchange flow, issuing a signed JWT
per Section 3 of the specification (header typ: "oauth-id-jag+jwt", claims including
iss, sub, aud, client_id, jti, exp, iat).
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 CEL expressions (building on the CEL infrastructure from #4601) is future work.
OIDC discovery is extended with identity_chaining_requested_token_types_supported per
Section 7 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 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.
Every ID-JAG token exchange request (issued or rejected) emits a structured log entry
with client_id, connector_id, audience, resource (if present), requested and
granted scope (these may differ after policy evaluation), sub, jti (if issued),
and the policy decision (approved/denied with reason like audience_not_allowed
or client_has_no_policy).
The following Prometheus counters are exposed:
dex_id_jag_requests_total (labels: result) — issued vs rejecteddex_id_jag_policy_rejections_total (labels: reason) —
breakdown by denial reason, useful for spotting misconfigurations or abusedex_id_jag_scope_modifications_total — cases where policy reduced the requested scopestyp: "oauth-id-jag+jwt" header and distinct
issued_token_type prevent confusion with ID Tokens or access tokens.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.audience parameter is newly introduced
(not previously used in the implementation despite DEP #2812's original proposal),
and connector_id already exists.subject_token_type
(urn:ietf:params:oauth:token-type:saml2)actor_token support for delegation scenariosjti tracking to prevent ID-JAG replay attacks