Skip to content

AUT-13975: feat: support requested_token_type and pre-signed jwt-bearer assertions - #159

Open
mschaab-SA wants to merge 3 commits into
masterfrom
feature/AUT-13975-id-jag-token-exchange-and-raw-assertion
Open

AUT-13975: feat: support requested_token_type and pre-signed jwt-bearer assertions#159
mschaab-SA wants to merge 3 commits into
masterfrom
feature/AUT-13975-id-jag-token-exchange-and-raw-assertion

Conversation

@mschaab-SA

@mschaab-SA mschaab-SA commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Jira: AUT-13975

Problem

oauth2c implements both the token-exchange and jwt-bearer grants, but neither parameter that Cross-App Access needs was reachable, so it could not drive either leg of the flow:

  • No requested_token_type. The parameter appears nowhere in the repo, so the minting leg could not ask for an ID-JAG.
  • jwt-bearer always minted its own assertion. RequestToken signs AssertionClaims(...) with --signing-key and sets that as assertion. Redeeming an ID-JAG means presenting a JWT somebody else signed, and there was no way to supply one.

--assertion could not be reused for the second case: it is validate:"omitempty,json" and holds claims to sign, so a raw JWT both fails validation and means the wrong thing.

Changes

  • --requested-token-type sets requested_token_type on the token-exchange grant.
  • --assertion-jwt supplies a pre-signed assertion, used verbatim, bypassing SignJWT.
  • LogAssertion no longer prints a bare Signing key heading when nothing was signed locally — the same != nil guard already used a few lines above.

requested_token_type is deliberately left without a validate tag, unlike the neighbouring SubjectTokenType/ActorTokenType oneof=s. RFC 8693 section 2.1 makes it an open URI namespace, so pinning the accepted values would mean editing this tool every time a draft mints a new token type.

Verification

Both legs driven end to end against a local SecureAuth CIAM server with the id_jag feature flag on — two tenants, the minting workspace on token-exchange and the redeeming workspace on jwt-bearer:

--grant-type urn:ietf:params:oauth:grant-type:token-exchange \
  --requested-token-type urn:ietf:params:oauth:token-type:id-jag \
  --subject-token "\$SUBJ" --audience "\$RESOURCE"
    -> issued_token_type=urn:ietf:params:oauth:token-type:id-jag, token_type=N_A

--grant-type urn:ietf:params:oauth:grant-type:jwt-bearer --assertion-jwt "\$JAG"
    -> access token issued by the resource workspace

A pleasant side effect of routing the grant through the existing assertion form field: LogAssertion already decodes and pretty-prints it, so the ID-JAG's claims show up in the flow output without any extra work.

Also verified the grant is refused (id_jag_audience_mismatch) when replayed at a different resource server, and that it can be redeemed repeatedly within its lifetime — both per the draft.

Unit tests cover the new form values and the pass-through, including that an unset --assertion-jwt still takes the signing path.

mschaab-SA and others added 2 commits August 19, 2026 14:50
Neither parameter that Cross-App Access needs was reachable. Token exchange
could not ask for an ID-JAG, and jwt-bearer always signed its own assertion
from --assertion, which holds claims to sign rather than a token.

requested_token_type is left unvalidated, unlike the adjacent subject and
actor token types: RFC 8693 section 2.1 makes it an open URI namespace, so a
oneof would mean editing this tool whenever a draft mints a new type.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A pre-signed assertion leaves request.SigningKey nil, so the flow printed a
bare "Signing key" heading with nothing under it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@mschaab-SA mschaab-SA left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review (Claude Code). 7 findings, ranked by severity — the top two were verified empirically (built binary / mutation testing) against this branch. Each inline comment is self-contained and actionable.

Comment thread cmd/log.go Outdated
Comment thread internal/oauth2/request_test.go
Comment thread internal/oauth2/oauth2.go Outdated
Comment thread internal/oauth2/oauth2.go Outdated
Comment thread internal/oauth2/request_test.go Outdated
Comment thread cmd/log.go
Comment thread internal/oauth2/request_test.go Outdated
…tion

Give the client assertion its own key field so it can no longer be
attributed to a grant assertion the client never signed, move the nil
handling into LogKey, validate the two new flags before any request is
sent, and widen the subject/actor token type lists to cover the ID-JAG
draft's id_token subject.

Tests: share the form-capturing server, and assert that an unset
--assertion-jwt really signs locally - the previous test passed on any
error, including a dial failure.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant