From 0d7e2f6b41fc9ef611763647689442b65eef67c4 Mon Sep 17 00:00:00 2001 From: tegwick Date: Mon, 7 Sep 2026 00:17:56 +0200 Subject: [PATCH] Document the authorization-code bindings for relying parties KEY-WP-0016 changed /token and /userinfo behaviour with no consumer-facing note; nothing in docs/ mentioned redirect_uri, so the change would have reached a deployment silently. States what an exchange must now send, who is affected and how to roll out. Every browser registration in dev-config is public with an authorization_code grant, so only the redirect_uri requirement can affect them; the realistic failure is a client that sends it to /authorize and omits it at /token, which has not been observed against a live consumer. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01NV9oijZukGyGbRQGGKnK4P Assistant: claude-code Assistant-Model: opus Assistant-Process: 713576@bnt-lap001 Assistant-Session: 384c511d-9bce-4cb8-a676-2aef6c0c8df6 --- SCOPE.md | 5 +++- docs/authorization-code-bindings.md | 45 +++++++++++++++++++++++++++++ workplans/ADHOC-2026-09-07.md | 34 ++++++++++++++++++++++ 3 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 docs/authorization-code-bindings.md create mode 100644 workplans/ADHOC-2026-09-07.md diff --git a/SCOPE.md b/SCOPE.md index e9766ef..902de56 100644 --- a/SCOPE.md +++ b/SCOPE.md @@ -50,7 +50,10 @@ Keycloak interchangeability are not established. access-token purpose (KEY-WP-0016). Upstream provider tokens from Authelia are still accepted on a transport-trust assumption without signature or issuer/audience verification; that gap remains open. Enforcing these bindings - is not complete profile conformance. See the assessment below. + is not complete profile conformance. Relying parties must repeat `redirect_uri` + on the token exchange and present the access token, not the ID token, to + `/userinfo`; see [authorization-code bindings](docs/authorization-code-bindings.md). + See the assessment below. - Tests cover local handlers, adapters, transformations and CLI protocol behavior. They do not establish complete replacement against a running Keycloak/full-LDAP stack. The Scenario B/C shell harnesses are incomplete. diff --git a/docs/authorization-code-bindings.md b/docs/authorization-code-bindings.md new file mode 100644 index 0000000..f305058 --- /dev/null +++ b/docs/authorization-code-bindings.md @@ -0,0 +1,45 @@ +# Authorization-code bindings and rollout note + +KEY-WP-0016 added four bindings to the `/token` authorization-code grant and +tightened `/userinfo` verification. Three of them can change the outcome of a +request that previously succeeded, so this note states exactly what a relying +party must send. It describes source behaviour; the deployed issuer only enforces +it once an image built from that revision or later is rolled out. + +## What the exchange must now send + +| Requirement | Behaviour | Effect on a conforming client | +| --- | --- | --- | +| `redirect_uri` on the token request | Must be present and byte-identical to the value sent to `/authorize` for that code. Comparison is exact — no normalization, no trailing-slash tolerance. | None. RFC 6749 section 4.1.3 already requires it whenever `/authorize` carried one, and KeyCape's `/authorize` always does. | +| Grant-type eligibility | A client whose registration lists `grantTypes` without `authorization_code` is refused on this path. An absent or empty `grantTypes` still means an authorization-code client, matching config validation. | None for the registrations in `config/dev-config.yaml`, which all declare `authorization_code`. A `client_credentials`-only client could previously reach this path and can no longer. | +| Confidential client authentication | A client registered as `clientType: confidential` must present its secret via `client_secret_basic`. A confidential registration with no configured secret is refused rather than accepting an empty one. | None for the current browser clients, which are all `public`. Relevant to the planned human approval registration if it is registered confidential. | +| Single-use codes | A code is consumed atomically at lookup, so exactly one concurrent exchange can succeed and a failed exchange does not leave a replayable code. | None for a client that exchanges each code once. A client that retried a failed exchange with the same code now gets `authorization code not found or expired`. | + +`/userinfo` additionally requires `alg: RS256` in the JOSE header, an `iss` equal +to this issuer, and an access token — an ID token presented as a bearer token is +now rejected. Present the access token, not the ID token. + +## Who this affects + +Every browser client in `config/dev-config.yaml` — `openbao-admin`, `demo-app`, +`netkingdom-bootstrap-console`, `user-engine-portal` and `coulomb-social` — is +`clientType: public` with `grantTypes: ["authorization_code"]`, so only the +`redirect_uri` requirement can affect them. Standard OIDC client libraries send +it, and the in-repo `keycape login` command already did, so no consumer change is +expected. + +The realistic failure is a client that sends `redirect_uri` to `/authorize` but +omits it at `/token`, which some libraries allow when a single redirect URI is +registered. That now returns HTTP 400 with feature `redirect_uri`. This has not +been observed against a live consumer; it is the failure mode to look for first +if a login that worked before the rollout starts failing at the token step. + +## Rolling out + +The change is in the issuer only — no registration, secret, key or claim changes, +and no configuration migration. Roll out the image and watch for 400s carrying +feature `redirect_uri` or `grant_type`, and 401s carrying feature +`Authorization`, on `/token`. Rolling back is a straight image revert; nothing +persisted changes shape. + +Evidence for each binding is in `src/internal/server/oidc/hardening_test.go`. diff --git a/workplans/ADHOC-2026-09-07.md b/workplans/ADHOC-2026-09-07.md new file mode 100644 index 0000000..7a530d6 --- /dev/null +++ b/workplans/ADHOC-2026-09-07.md @@ -0,0 +1,34 @@ +--- +id: ADHOC-2026-09-07 +type: workplan +title: "Document the KEY-WP-0016 authorization-code bindings for consumers" +domain: infotech +repo: key-cape +status: finished +owner: claude +created: "2026-09-07" +updated: "2026-09-07" +--- + +## Publish the consumer-facing rollout note + +```task +id: ADHOC-2026-09-07-T01 +status: done +priority: medium +``` + +KEY-WP-0016 changed `/token` and `/userinfo` behaviour without a consumer-facing +note; no document in `docs/` mentioned `redirect_uri` at all, so the change would +have reached a deployment silently. + +Published `docs/authorization-code-bindings.md` stating what an exchange must +send, who is affected and how to roll out. Verified the blast radius rather than +assuming it: all five browser registrations in `config/dev-config.yaml` +(`openbao-admin`, `demo-app`, `netkingdom-bootstrap-console`, +`user-engine-portal`, `coulomb-social`) are `clientType: public` with +`grantTypes: ["authorization_code"]`, so the grant-type and confidential-client +bindings cannot affect them and only the `redirect_uri` requirement can. Named +the realistic failure — a client that sends `redirect_uri` to `/authorize` but +omits it at `/token` — and said plainly that it has not been observed against a +live consumer. Referenced the note from SCOPE.md.