Skip to content

Support authorization_details on token exchange - #6921

Merged
jhrozek merged 4 commits into
mainfrom
authorization_details_v2
Oct 22, 2026
Merged

jhrozek merged 4 commits into
mainfrom
authorization_details_v2

Conversation

@jhrozek

@jhrozek jhrozek commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Delegated clients such as Mecatl need to name a session or schedule grant with an RFC 9396 authorization_details entry when they call token exchange. The auth server ignores the parameter today, so there is no way for a deployment to validate or grant these details. This PR adds the parsing and hook mechanism and wires it into the token-exchange handler. It stops there: details are not put in tokens, persisted or sent to resource servers yet.

  • New pkg/authserver/authorizationdetails package: an immutable, Go-only registry mapping a type name to the grant types it applies to and a Grant hook. Parsing enforces the RFC 9396 structure plus ToolHive policy (duplicate and case-folded member names, 64 KiB size limit, depth 64). Each hook is called once with all of its entries; its output is validated to the same limits and returned in a fixed order.
  • Token-exchange handler: with a registry configured, a repeated parameter is rejected, the details are parsed before the subject token is checked, and the hooks run after the subject, consent, scope and audience checks. The response echoes what the hooks granted, only after the token is issued.
  • The registry is passed through a functional factory option, authserver.Config and runner.WithAuthorizationDetailsRegistry. Setting it while token exchange is disabled is a startup error.
  • Each rejection is logged once at WARN with a fixed reason. Hook error text is never logged, since it could carry credentials or request data.
  • docs/arch/17-token-exchange-delegation.md describes the behaviour and its limits.

Fixes #6894

Related: #6886, #6895, #6897, #6909.

Type of change

  • New feature

Test plan

Not yet run on this branch. Unit and integration tests are included (package tests, handler_test.go, runner and config tests, and authorization_details_integration_test.go), but they have not been executed, and neither has lint. I will check the boxes below as each is run.

  • Unit tests (task test)
  • E2E tests (task test-e2e)
  • Linting (task lint-fix)
  • Manual testing (describe below)

Changes

File Change
pkg/authserver/authorizationdetails/ New registry, parsing, size/depth scanning and typed errors, with tests
pkg/authserver/server/tokenexchange/authdetails.go Handler-side parse, hook invocation and error mapping
pkg/authserver/server/tokenexchange/{handler,factory,validator}.go Wire the registry into the handler and factory
pkg/authserver/{config.go,server_impl.go} Config field and option plumbing
pkg/authserver/runner/embeddedauthserver.go WithAuthorizationDetailsRegistry, startup check
docs/arch/ Doc 17 section and README entry
*_test.go Handler, config, runner and integration tests

Does this introduce a user-facing change?

Before: authorization_details on a token-exchange request is ignored, and the response never contains the member.

After, with no registry configured: nothing changes.

After, with a registry configured: a client can receive invalid_request (repeated parameter, or a hook's typed rejection with a safe reason), invalid_authorization_details, or server_error for a hook failure. A successful response includes an authorization_details member with what the hooks granted.

Special notes for reviewers

  • This is two commits. The first (the package) is self-contained and compiles and tests on its own, so it can be reviewed separately. Non-test size is roughly 455 lines for the package (much of it doc comments) and about 290 for the wiring.
  • The history was rebuilt from an earlier unpushed branch whose first commits introduced a design that later commits rewrote.
  • Deliberate deviations: other grant types ignore the parameter, nothing is advertised in discovery, and granted details are echoed but not conveyed to resource servers yet (Storage for grants issued by token exchange #6895, General RFC 9396 support in the authorization server #6909).

@github-actions github-actions Bot added the size/XL Extra large PR: 1000+ lines changed label Oct 22, 2026
@codecov

codecov Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.90110% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.88%. Comparing base (6cbec29) to head (05e8b19).

Files with missing lines Patch % Lines
...erver/authorizationdetails/authorizationdetails.go 98.82% 1 Missing ⚠️
pkg/authserver/authorizationdetails/errors.go 80.00% 1 Missing ⚠️
pkg/authserver/runner/embeddedauthserver.go 94.44% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6921      +/-   ##
==========================================
+ Coverage   79.87%   79.88%   +0.01%     
==========================================
  Files         829      833       +4     
  Lines       84722    84976     +254     
==========================================
+ Hits        67668    67881     +213     
- Misses      17049    17090      +41     
  Partials        5        5              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

ChrisJBurns
ChrisJBurns previously approved these changes Oct 22, 2026
jhrozek and others added 4 commits October 22, 2026 20:25
Token exchange needs RFC 9396 authorization_details support for delegated
clients (#6894). Add a Go-only, immutable registry that maps a type name to
the grant types it applies to and a Grant hook. Parsing enforces the RFC 9396
structure plus ToolHive policy: duplicate and case-folded member names, a
64 KiB limit and depth 64. Grant calls each hook once with all of its
entries and the request context, validates the hook output to the same
limits, and returns the granted details in a fixed order. Hooks decide only;
the package does not persist details or put them in tokens.

Co-authored-by: Mecatl <noreply@mecatl.dev>
Delegated clients such as Mecatl name a session or schedule grant with an
authorization_details entry on token exchange (#6894). With a registry
configured, the handler rejects a repeated parameter with invalid_request
(RFC 6749 section 5.2), parses the details before the subject token is
checked, and calls the type hooks after the subject, consent, scope and
audience checks. The response echoes what the hooks granted, only after the
token is issued. Typed hook errors become invalid_authorization_details or
invalid_request with a safe reason; other hook failures become server_error.
Each rejection is logged once at WARN with a fixed reason; hook error text
is never logged, since it could carry credentials or request data. Without
a registry the parameter is ignored, as before.

The registry is passed through a functional factory option,
authserver.Config and runner.WithAuthorizationDetailsRegistry. Setting it
while token exchange is disabled is a startup error. Docs describe the
behaviour and its limits: details are not put in tokens, persisted or sent
to resource servers yet (#6909, #6895).

Co-authored-by: Mecatl <noreply@mecatl.dev>
CI's Go lint job flagged a long line, a few unused parameters and
receivers, a missing t.Helper(), and missing t.Parallel() calls in the new
authorization_details tests. Fix the ones that are mechanical. Two tests
keep their serial execution with a //nolint reason: one mutates the
process-wide slog default, and the other's subtests share state and must
run in order.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
CI's lint job flagged validArray (complexity 20) and
HandleTokenEndpointRequest (complexity 18), both over the gocyclo limit of
15. Split validArray into an arrayScanner with one method per token kind,
and move the consent, scope, audience and authorization_details steps into
authorizeDelegation. The check order is unchanged and no error or log
reason changes.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
@jhrozek
jhrozek force-pushed the authorization_details_v2 branch from 04e339e to 05e8b19 Compare October 22, 2026 18:34
@github-actions github-actions Bot added size/XL Extra large PR: 1000+ lines changed and removed size/XL Extra large PR: 1000+ lines changed labels Oct 22, 2026
@jhrozek
jhrozek merged commit 76b09c0 into main Oct 22, 2026
84 of 100 checks passed
@jhrozek
jhrozek deleted the authorization_details_v2 branch October 22, 2026 19:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/XL Extra large PR: 1000+ lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support authorization_details (RFC 9396) in token exchange

2 participants