Problem Statement
wso2 login works against Asgardeo and WSO2 Identity Server 7.3.0. It does not work against Thunder (ThunderID), and nobody can say whether making it work is a documentation exercise or a change to the shell's authentication broker.
A person running a Thunder-backed deployment — today that is anyone using Agent Manager, whose bundled authorization server is Thunder — has no way to authenticate the shell against it. There is no registration walkthrough, no deployment description the live runs can read, and no evidence that the broker's derivation strategy survives contact with the product.
The uncertainty is the expensive part. docs/research/product-authentication-compatibility.md concluded that Thunder needs a token-exchange+resource downscoping strategy, while the broker implements scoped-refresh and nothing else. If that conclusion holds, this is production code in internal/auth — a second derivation mechanism, a way for a context to say which one applies, and a refusal path when a deployment cannot do either. If it does not hold, this is a walkthrough and a deployment block.
That conclusion is dated. It was drawn from Thunder's protocol documentation, not from a running deployment, and Thunder is pre-1.0 — v1.0.0-alpha2, a repository created in May 2025 whose grant handlers and management APIs are still moving. Designing against it without measuring would commit the shell to a mechanism chosen from a document.
There is a second, sharper unknown that the research does not address at all. The broker's narrowing has two halves: it proves the issued permissions are exactly the ones requested, and it proves the token is bound to the audience requested. The research treats Thunder as a narrowing problem. It may instead be an audience-binding problem, a scope-model problem, or nothing at all.
Solution
Measure first, then build only what the measurement forces.
A spike stands up a Thunder deployment, registers an application, and answers three questions the shell's behaviour actually depends on: does Thunder honour a narrower scope on the refresh grant; what lands in an access token's aud; and can a product-specific audience be obtained without token exchange. Those are the same three questions already answered for Asgardeo and Identity Server 7.3.0, so Thunder's answers slot into the existing per-product verdict table rather than starting a new one.
A pre-committed rule reads the measurement, so the mechanism cannot be chosen after the fact to justify work already imagined. Candidate mechanisms are ordered by cost, cheapest first, and the cheapest that satisfies the broker's verification wins. Where two mechanisms both work, the one Thunder's own documentation commits to wins, because a behaviour that happens to work in an alpha is not a foundation.
What ships in every outcome: a registration walkthrough for Thunder, a deployment block the live runs can read, re-runnable experiments that produce the verdicts, and the verdicts themselves recorded against the deployment and date that produced them. What ships only if measurement forces it: a way for a context to declare which derivation a deployment supports, and the derivation itself.
The slice also closes a gap the first slice left open. The shell's non-interactive path — a CI job authenticating with client credentials — has never been exercised against a live deployment of any product. It is proven only against an in-process fake issuer. Thunder gets the first live run of that path, because the two paths share one verification gate and a mechanism that serves one must serve both.
User Stories
Logging in against Thunder
- As an operator of a Thunder-backed deployment, I want
wso2 login to complete against Thunder, so that I can run product commands without a separately obtained token.
- As an operator, I want a registration walkthrough written for Thunder specifically, so that I am not guessing which Asgardeo or Identity Server step translates.
- As an operator, I want the walkthrough to name the Thunder version it was written against, so that I can tell a renamed console control from a broken deployment.
- As an operator, I want a container recipe I can paste, so that I can reach a working deployment without reading Thunder's own installation documentation first.
- As an operator, I want the recipe to state that Thunder runs standalone, so that I do not stand up a database and a cache it does not need.
- As an operator, I want to be told what Thunder's issuer value looks like and how to confirm it, so that I do not spend a browser sign-in discovering the shell could not read the deployment's configuration.
- As an operator, I want the TLS trust step spelled out for Thunder, so that login can reach discovery at all on a deployment serving a self-signed certificate.
- As an operator, I want to be told what trusting that certificate does and does not grant, so that I can decide whether to make the trade.
- As an operator on macOS, I want to be told that Go ignores the usual certificate environment variable there, so that I do not try the approach that cannot work.
- As an operator, I want to know which value belongs in a context's product audience on Thunder, so that I do not paste Asgardeo's answer and end in a refusal.
- As an operator, I want the walkthrough to say how to create a user who can sign in and hold the required permissions, so that login has an account to complete against.
Using a Thunder session
- As a product module author, I want a grant derived from a Thunder session to carry exactly the permissions my module asked for, so that my module never holds authority nobody decided to give it.
- As a product module author, I want a grant derived from a Thunder session to be bound to the audience my module asked for, so that the token is not rejected later for a reason nobody can diagnose from where it fails.
- As a product module author, I want the shell to refuse rather than hand over a broader grant, so that the guarantee my module relies on holds on Thunder exactly as it does elsewhere.
- As a product module author, I want a refusal to be a typed problem in the authentication class with a stable code, so that automation can tell an access failure from a product failure by exit code alone.
- As a product module author, I want no refusal or grant to reveal where a credential came from, so that the module contract's boundary holds regardless of which deployment is behind it.
- As an operator, I want a refusal on Thunder to name both what was asked for and what was issued, so that I can go and fix a registration rather than guess at one.
Non-interactive and CI
- As a CI job author, I want a client-credentials identity to work against Thunder, so that automation reaches Thunder-backed products without a browser.
- As a CI job author, I want the client-credentials path to receive the same narrowing guarantee as the browser path, so that a module cannot tell which kind of context invoked it and does not need to.
- As a CI job author, I want the walkthrough to cover registering a machine-to-machine application in Thunder, so that the automation path is as documented as the interactive one.
- As a CI job author, I want the client secret to be read from an environment variable a context names, so that the secret stays in job memory and never reaches disk or shell state.
- As a maintainer, I want the live CI run to source its secret from the environment and never from a deployment description file, so that the harness's own rule against secrets in those files is not quietly broken by the run that needed one.
- As a maintainer, I want the deployment description to point at the running instructions rather than name the secret's variable, so that nobody is invited to paste a value where a name appears.
Measuring and recording
- As a maintainer, I want the spike to precede any design, so that the mechanism is chosen from a running deployment rather than from a document about a pre-1.0 product.
- As a maintainer, I want the decision rule written down before the measurement runs, so that the result cannot be read to justify work already imagined.
- As a maintainer, I want the refresh-grant narrowing and access token audience questions measured on Thunder whichever mechanism wins, so that the per-product verdict table has no hole where Thunder's column should be.
- As a maintainer, I want each verdict recorded with its date and the deployment that produced it, so that a Thunder verdict cannot be mistaken for an Identity Server one.
- As a maintainer, I want the experiments re-runnable by anyone with a container, so that a verdict rests on something reproducible rather than on one person's transcript.
- As a maintainer, I want the experiments to live behind the build tag that keeps live runs out of the default gate, so that nothing here can be pulled into a run that needs no credentials.
- As a maintainer, I want a make target that runs the Thunder experiments and prints their verdicts, so that producing evidence is one command.
- As a maintainer, I want live runs never answered from the test cache, so that a cached pass cannot report a deployment as working without having contacted it.
- As a maintainer, I want a mechanism deliberately not measured to be recorded with the reason, so that a later reader does not repeat the experiment or assume it failed.
- As a maintainer, I want the reason Thunder has no device authorization grant recorded, so that a future slice does not plan a device flow against it.
Describing a deployment
- As a maintainer, I want to describe a Thunder deployment in the same file and the same shape as Asgardeo and Identity Server, so that switching deployments does not need a fresh terminal or a different procedure.
- As a maintainer, I want the Thunder block to be additive to the existing deployment descriptions, so that concurrent work on the same file does not conflict.
- As a maintainer, I want the deployment description validated before any browser opens, so that a misread variable or an unreadable context document does not waste a person's attention mid-sign-in.
- As a maintainer, I want a malformed Thunder description reported as a failure and an absent one reported as a skip, so that a deployment somebody believed they had configured is never silently passed over.
- As a maintainer, I want the smoke context and its stored session to stay distinct from anything chosen by hand, so that a live run cannot overwrite a real session and a cleanup cannot delete one.
If measurement forces a new derivation
- As an operator, I want to declare in a context which identity provider an identity points at, so that I write down what I know rather than what the shell knows.
- As an operator, I want that declaration to imply the derivation the deployment supports, so that I do not have to learn a protocol mechanism to write a working context.
- As an operator, I want to be able to state the derivation explicitly when my deployment does not match its product's default, so that a Thunder without token exchange configured is expressible.
- As an operator, I want an explicit derivation to win over the one implied by the identity provider, so that the override is worth writing.
- As an operator, I want both declarations to be optional, so that every context document that works today keeps working unchanged.
- As an operator, I want an issuer that is not a WSO2 product to remain describable, so that the shell stays usable against any conforming OpenID provider.
- As an operator, I want naming the wrong derivation to fail on the first request with a refusal that says which derivation was attempted, so that a registration mistake costs one request rather than one browser sign-in.
- As a maintainer, I want the derivation to apply wherever a grant is made rather than only on the interactive path, so that the same context document does not behave differently depending on how somebody logged in.
If measurement does not force one
- As a maintainer, I want no configuration field added that the shell does not read, so that the document does not gain a second member that is validated and never consumed.
- As a maintainer, I want the case for such a field filed with the measurement attached, so that whoever needs it later inherits the reasoning rather than rediscovering it.
- As a maintainer, I want a third product served unchanged by the existing derivation to be recorded as evidence the design is right, so that the absence of new code is a finding rather than an omission.
Reviewing and maintaining
- As a reviewer, I want the measurement and the code it justifies in one change, so that I can see the evidence and its consequence together.
- As a reviewer, I want the Thunder walkthrough kept out of the shared login guide's numbering, so that adding it does not renumber sections other work is editing.
- As a maintainer, I want the asymmetry of one product documented separately from two to be filed as a follow-up, so that it is a recorded decision rather than a wart.
- As a maintainer, I want the shell to gain no product-specific branch unless a measurement forced it, so that the broker stays a protocol client rather than becoming a compatibility matrix.
Implementation Decisions
Sequence
Work proceeds spike-first. A throwaway spike stands up a pinned Thunder container, registers an application through the Thunder console, and measures. Only after the measurement is in hand is any production code written. The decision rule below is recorded on this issue before the spike runs.
The spike itself is disposable and lives outside the repository. The experiments that back a recorded verdict are then ported into the smoke package. Porting is part of this slice's definition of done, not a follow-up.
Everything lands as one pull request, linked to this issue.
The decision rule
Candidate derivation mechanisms, ordered by cost:
| # |
Mechanism |
Cost if it wins |
| 1 |
Scoped refresh, audience supplied by a resource registered on the application |
No broker change. Documentation and live-run support only |
| 2 |
Scoped refresh plus an RFC 8707 resource parameter on the token request |
Small: one added request member, at both grant sites |
| 3 |
resource on the authorization request at login |
Not measured — deliberately |
| 4 |
Token exchange (RFC 8693) with resource |
A new derivation, its own refusal mapping, and tests |
The cheapest mechanism whose result satisfies the broker's existing verification wins. Where two both satisfy it, the one Thunder's own documentation commits to wins, on the grounds that an undocumented behaviour in an alpha is not a foundation.
Mechanisms 1 and 2 are measured regardless of which wins, because those are the two questions the per-product verdict table already asks of Asgardeo and Identity Server, and a Thunder column that skipped them would be a hole rather than an answer.
Mechanism 3 is excluded by design, not by measurement: binding the audience at authorization time binds a login session to one resource, and a session is one login serving several product namespaces. The exclusion and its reason are recorded.
Where mechanism 2 applies, the resource value is the product audience the context document already carries. No new member is needed to hold it; a declaration is needed only to select the behaviour.
Modules modified
internal/auth — only if the measurement selects mechanism 2 or 4. The unexported source abstraction already answers "how is access obtained"; a new mechanism becomes another implementation behind it, or a variation within the two existing sources. The verification gate is unchanged in either case: it proves the issued permissions match the request, the token is bound to the requested audience, and a lifetime was stated.
internal/contexts — only if the measurement selects mechanism 2 or 4. See the schema decision below.
test/smoke — always. A Thunder deployment description, a fixed constant naming the client secret's environment variable, ported experiments, and a live non-interactive run.
- Documentation — always. A Thunder walkthrough, a pointer to it from the login guide, Thunder verdict cells in the redirect-URI-and-scope-narrowing research document, and running instructions for the new live run.
- Build targets — always. A target that runs the Thunder experiments and prints their verdicts, alongside the existing per-product targets.
Schema, conditional on measurement
If and only if the measurement selects mechanism 2 or 4, an identity's authentication arrangement gains two optional members:
- one naming the identity provider the identity points at;
- one naming the derivation the deployment supports.
Rules:
- Both are optional. Absence of both means the derivation that ships today — scoped refresh — so every existing context document remains valid and unchanged. This is additive to the current schema version; no migration.
- The identity provider defaults the derivation. An explicitly stated derivation overrides that default, and the pair is never refused. This is a default with an override, not two contradictory assertions, and is deliberately distinct from the schema's existing treatment of members that contradict a kind — those are refused.
- The override exists because Thunder is pre-1.0: a deployment without token exchange configured is a state the first several Thunders will be in, and forcing that case to disown its identity provider would discard the diagnostics the declaration exists to give.
- An issuer that is not a WSO2 product omits the identity provider entirely. Absence is the open-world case.
If the measurement selects mechanism 1, neither member ships. A field the broker does not read is a field that misrepresents itself as load-bearing, and the identity type member already in the schema — validated on every read, consumed nowhere — is the standing demonstration. In that outcome the design above is filed as its own issue with the measurement attached.
This is a real architectural line: today no product name appears anywhere in the broker. Asgardeo and Identity Server differ in what reaches an access token's audience claim, and the broker needed no knowledge of either — the context document differs, and verification catches anything wrong. Adding an identity-provider member makes some product the first the broker knows by name, and that should be forced by evidence rather than chosen in advance.
Coverage of the non-interactive path
Both the interactive and the non-interactive sources call the same verification gate. Whatever the measurement selects therefore applies at both grant sites. Applying it at one and not the other would make the same context document behave differently depending on how somebody logged in, which is a defect shaped like a design decision.
This slice additionally adds the first live run of the non-interactive path against any deployment. Today that path is proven only against the in-process fake issuer, on every product.
Secret handling for the live non-interactive run
The client secret never reaches a file. The smoke package carries a fixed constant naming the environment variable, in the same way it already carries a fixed secure-store reference chosen so a live run cannot collide with a real one. The running instructions tell an operator to export the value; the deployment description file names neither the value nor the variable, and carries only a comment pointing at those instructions.
This preserves the harness's written position that a client secret in a casually-shared file is a mistake, and it exercises the production contract exactly as shipped: a context names a variable, the broker reads it into process memory, and the value never reaches shell state, the secure store, or a module's environment.
Documentation placement
The Thunder walkthrough is a separate guide, reached by a single added line in the existing login guide.
The structurally correct placement is a new numbered section beside Asgardeo and Identity Server. That is rejected here for a specific reason: it renumbers six subsequent sections, requiring cross-reference edits inside the login guide and in the smoke harness's running instructions and deployment description — three files under concurrent edit by parallel work on device login and reference-module token tests.
Two supporting reasons make the compromise defensible on its own terms: the login guide already exceeds nine hundred lines and a third walkthrough pushes it past thirteen hundred; and Thunder at v1.0.0-alpha2 will churn far more than the other two, with churn cheapest in a file nothing cross-references.
A follow-up issue is filed to split all three product walkthroughs symmetrically once the parallel work has landed.
Walkthrough form
The Thunder walkthrough is a console click-through, matching the genre of the Asgardeo and Identity Server sections. Thunder ships an administrative console, so this is available.
The known cost is that a click-through against an alpha console goes stale faster than a request-based recipe, and when it does a reader cannot distinguish a moved control from a broken deployment. This is mitigated by pinning an explicit Thunder image tag in the container recipe and naming that version in the walkthrough, so a reader can tell which console the steps described.
Facts established against a running Thunder before this spec
- Discovery advertises the grant types the research predicted: authorization code, client credentials, refresh token, token exchange, backchannel authentication, and JWT bearer. No device authorization grant and no device authorization endpoint.
- Discovery advertises nothing about RFC 8707 resource indicators. Support is therefore not discoverable; it can only be declared or attempted-and-verified. This is what rules out deriving the mechanism from deployment metadata.
- The issuer is the bare origin, unlike Identity Server's path-suffixed issuer. Discovery resolution needs no change: the shell resolves an issuer's endpoints through a standard OpenID discovery client that validates the document against the issuer it was fetched from, and Thunder satisfies that unchanged.
- Thunder runs standalone on embedded storage. The container recipe is a single run command, at parity with the Identity Server recipe.
- Thunder serves TLS with a minimum version of 1.3 and a certificate generated per deployment at setup. The trust-store step applies, but not Identity Server's caveats about a shipped certificate authority whose private key is published — a materially better story that should be stated rather than copied across.
- The
latest image tag currently carries an earlier alpha than the newest release. An explicit tag is pinned; if the newest release has no published image, the spike measures what exists and the walkthrough says so.
Testing Decisions
What makes a good test here
A test drives external behaviour through the highest available entry point and asserts on what a caller observes: the grant handed over, or the typed problem refused with. It does not reach into how a grant was obtained. The existing tests in the authentication package are the model — they assert that a session is narrowed to what a module asked for, that a broader grant is refused rather than accepted, that a rotated credential is persisted before a grant is returned, and that no refusal carries session material. None of them names a derivation mechanism.
Live runs are a separate category. They are guarded by a build tag, never execute in the default gate, and are never answered from the test cache — a cached pass would report a deployment as working without having contacted it.
Seams
No new seams. Three existing ones, one extended.
-
The broker's acquisition entry point, driven against the in-process fake issuer. This is the primary and only seam for new derivation behaviour. If the measurement selects mechanism 2 or 4, Thunder's behaviour is modelled by new options on the fake issuer — the same pattern that already models refresh-grant scope handling, client-credentials scope handling, audience contents, refresh-token rotation, an omitted scope member, loopback port flexibility, and a malformed signing certificate. Assertions go through the fake issuer's introspection helper, which already reports whether a token is active and what permissions and audiences it carries.
-
The smoke package's deployment configuration and the context document it builds. Untagged and inside the default gate. This is already the seam that proves a live run's document is readable by the shell before any browser opens. The Thunder deployment description, the identity type it implies, and the non-interactive document shape are proven here.
-
The live runs behind the smoke build tag. Outside the default gate. The existing interactive login run is untouched. Two runs are added: the Thunder experiments, and the first live non-interactive acquisition.
Explicitly not added: an exported derivation-strategy type in the authentication package. The existing unexported source abstraction already answers the question a strategy type would answer, and exporting one so a test could reach it would add a second seam to a package that has one. Any new mechanism is proven through the broker's acquisition entry point.
Modules tested
- The authentication broker — new behaviour only if the measurement forces it. Coverage would mirror the existing narrowing tests: exactly-what-was-asked-for is granted; a broader issuance is refused; a deployment that will not narrow is refused with the narrowing-unavailable code; the refusal names both sides of the mismatch; no refusal carries credential or session material; the guarantee is identical on the interactive and non-interactive paths.
- The context schema — new behaviour only if the measurement forces it. Coverage would establish that both new members are optional, that absence of both yields the derivation that ships today, that the identity provider defaults the derivation, that an explicit derivation overrides it, that the pair is not refused when they differ, and that documents valid today remain valid.
- The smoke configuration — always. Coverage mirrors the existing configuration tests: a Thunder description is read correctly; a malformed one is a failure and an absent one a skip; the document built from it is readable by the shell; the context stays in the identity's home tenant; declared capabilities are exactly what the run requests and nothing wider.
Prior art
- Narrowing and refusal behaviour: the browser-source and inline-source test files in the authentication package.
- Broker policy and denial safety: the broker's own test file, particularly the tests that no denial reveals a source credential and that identity kind decides which policy applies.
- Deterministic issuer behaviour: the fake issuer's options, which already model six distinct deployment behaviours through configuration rather than through separate fakes.
- Live experiments and verdict reporting: the existing Asgardeo empirical runs, which print verdict lines prefixed by the question and a deployment line beneath, and which name the research document section the verdict belongs in.
- Live login: the existing interactive smoke run.
- Configuration reading: the smoke configuration tests, which prove skip-versus-failure separation and document readability.
Out of Scope
- Device authorization grant against Thunder. Thunder registers no device grant handler and advertises no device authorization endpoint. This is confirmed against a running deployment, not only from source. Parallel work covers device login against Asgardeo and Identity Server; nothing here plans around a Thunder device flow.
- CIBA / backchannel authentication. Thunder's only decoupled grant. Not a login mechanism this shell implements, and adding one is its own slice.
- Personal access token identities. Not implemented for any product.
- Organization switching. Unchanged; a context that targets an organization outside its identity's home tenant is still refused.
- Resource-first discovery (RFC 9728). Carried over as a known gap, unrelated to this slice.
- What the shell should claim when a deployment cannot bind a token to one product. An open issue owns that decision. This slice records what Thunder does and does not pre-empt the answer.
- Splitting the Asgardeo and Identity Server walkthroughs into their own guides. Filed as a follow-up; doing it here would collide with parallel work in the same file.
- Live non-interactive runs against Asgardeo and Identity Server. This slice adds the first such run, against Thunder only. Extending it to the other two products is a separate issue, so that Thunder does not become the sole product with coverage the mature ones lack — that gap is recorded, not closed here.
- Changing the verification gate. The shell verifies what it was issued and refuses what it cannot prove. That behaviour is correct under every outcome below and nothing here proposes relaxing it into a fallback.
Further Notes
The outcome that changes the conversation
The largest unknown is not the derivation mechanism. It is whether Thunder has an API-resource-and-scopes model at all comparable to the other two products. A running Thunder advertises only the standard OpenID permission set. Asgardeo and Identity Server both allow registering product permissions against an API resource and issuing them to an application.
If Thunder's model differs enough that the reference module's permissions cannot be registered and issued, then every mechanism in the decision rule fails on the permissions half of verification before audience binding is reached, and this stops being a narrowing question. That outcome is escalated rather than worked around.
Relationship to prior research
- The landscape research records Thunder's grant list, its resource-indicator support, and its pre-1.0 status. Confirmed against a running deployment for the grant list; the resource-indicator claim is not observable from discovery metadata and is one of the things the spike measures.
- The compatibility research concluded token-exchange-plus-resource for Thunder. That conclusion is treated as a hypothesis to test, not a design input. It was drawn from a documentation page about token exchange, which is a fact about which page was read rather than about what the server accepts on other grants — RFC 8707 places the
resource parameter on authorization and token requests generally, not on token exchange specifically.
- The redirect-URI-and-scope-narrowing research holds the per-product verdict cells for Asgardeo and Identity Server 7.3.0. Thunder's cells join it in the same shape, each carrying a verdict, a date, and the deployment that produced it.
Conventions
Typed problems with stable codes and authentication-class exit codes. Conventional commits. Commits GPG-signed. The full deterministic gate — tests, vet, lint, and the architecture-proof acceptance run — passes before pushing.
Concurrency with parallel work
Parallel sessions are working on device login and on reference-module token tests. Expected shared surfaces are the login guide, the smoke deployment description, and the smoke configuration. Edits to all three are additive and localised by design: a single pointer line in the login guide, an appended deployment block, and added constants and fields in the configuration.
Problem Statement
wso2 loginworks against Asgardeo and WSO2 Identity Server 7.3.0. It does not work against Thunder (ThunderID), and nobody can say whether making it work is a documentation exercise or a change to the shell's authentication broker.A person running a Thunder-backed deployment — today that is anyone using Agent Manager, whose bundled authorization server is Thunder — has no way to authenticate the shell against it. There is no registration walkthrough, no deployment description the live runs can read, and no evidence that the broker's derivation strategy survives contact with the product.
The uncertainty is the expensive part.
docs/research/product-authentication-compatibility.mdconcluded that Thunder needs a token-exchange+resource downscoping strategy, while the broker implements scoped-refresh and nothing else. If that conclusion holds, this is production code ininternal/auth— a second derivation mechanism, a way for a context to say which one applies, and a refusal path when a deployment cannot do either. If it does not hold, this is a walkthrough and a deployment block.That conclusion is dated. It was drawn from Thunder's protocol documentation, not from a running deployment, and Thunder is pre-1.0 —
v1.0.0-alpha2, a repository created in May 2025 whose grant handlers and management APIs are still moving. Designing against it without measuring would commit the shell to a mechanism chosen from a document.There is a second, sharper unknown that the research does not address at all. The broker's narrowing has two halves: it proves the issued permissions are exactly the ones requested, and it proves the token is bound to the audience requested. The research treats Thunder as a narrowing problem. It may instead be an audience-binding problem, a scope-model problem, or nothing at all.
Solution
Measure first, then build only what the measurement forces.
A spike stands up a Thunder deployment, registers an application, and answers three questions the shell's behaviour actually depends on: does Thunder honour a narrower scope on the refresh grant; what lands in an access token's
aud; and can a product-specific audience be obtained without token exchange. Those are the same three questions already answered for Asgardeo and Identity Server 7.3.0, so Thunder's answers slot into the existing per-product verdict table rather than starting a new one.A pre-committed rule reads the measurement, so the mechanism cannot be chosen after the fact to justify work already imagined. Candidate mechanisms are ordered by cost, cheapest first, and the cheapest that satisfies the broker's verification wins. Where two mechanisms both work, the one Thunder's own documentation commits to wins, because a behaviour that happens to work in an alpha is not a foundation.
What ships in every outcome: a registration walkthrough for Thunder, a deployment block the live runs can read, re-runnable experiments that produce the verdicts, and the verdicts themselves recorded against the deployment and date that produced them. What ships only if measurement forces it: a way for a context to declare which derivation a deployment supports, and the derivation itself.
The slice also closes a gap the first slice left open. The shell's non-interactive path — a CI job authenticating with client credentials — has never been exercised against a live deployment of any product. It is proven only against an in-process fake issuer. Thunder gets the first live run of that path, because the two paths share one verification gate and a mechanism that serves one must serve both.
User Stories
Logging in against Thunder
wso2 loginto complete against Thunder, so that I can run product commands without a separately obtained token.Using a Thunder session
Non-interactive and CI
Measuring and recording
Describing a deployment
If measurement forces a new derivation
If measurement does not force one
Reviewing and maintaining
Implementation Decisions
Sequence
Work proceeds spike-first. A throwaway spike stands up a pinned Thunder container, registers an application through the Thunder console, and measures. Only after the measurement is in hand is any production code written. The decision rule below is recorded on this issue before the spike runs.
The spike itself is disposable and lives outside the repository. The experiments that back a recorded verdict are then ported into the smoke package. Porting is part of this slice's definition of done, not a follow-up.
Everything lands as one pull request, linked to this issue.
The decision rule
Candidate derivation mechanisms, ordered by cost:
resourceparameter on the token requestresourceon the authorization request at loginresourceThe cheapest mechanism whose result satisfies the broker's existing verification wins. Where two both satisfy it, the one Thunder's own documentation commits to wins, on the grounds that an undocumented behaviour in an alpha is not a foundation.
Mechanisms 1 and 2 are measured regardless of which wins, because those are the two questions the per-product verdict table already asks of Asgardeo and Identity Server, and a Thunder column that skipped them would be a hole rather than an answer.
Mechanism 3 is excluded by design, not by measurement: binding the audience at authorization time binds a login session to one resource, and a session is one login serving several product namespaces. The exclusion and its reason are recorded.
Where mechanism 2 applies, the
resourcevalue is the product audience the context document already carries. No new member is needed to hold it; a declaration is needed only to select the behaviour.Modules modified
internal/auth— only if the measurement selects mechanism 2 or 4. The unexported source abstraction already answers "how is access obtained"; a new mechanism becomes another implementation behind it, or a variation within the two existing sources. The verification gate is unchanged in either case: it proves the issued permissions match the request, the token is bound to the requested audience, and a lifetime was stated.internal/contexts— only if the measurement selects mechanism 2 or 4. See the schema decision below.test/smoke— always. A Thunder deployment description, a fixed constant naming the client secret's environment variable, ported experiments, and a live non-interactive run.Schema, conditional on measurement
If and only if the measurement selects mechanism 2 or 4, an identity's authentication arrangement gains two optional members:
Rules:
If the measurement selects mechanism 1, neither member ships. A field the broker does not read is a field that misrepresents itself as load-bearing, and the identity type member already in the schema — validated on every read, consumed nowhere — is the standing demonstration. In that outcome the design above is filed as its own issue with the measurement attached.
This is a real architectural line: today no product name appears anywhere in the broker. Asgardeo and Identity Server differ in what reaches an access token's audience claim, and the broker needed no knowledge of either — the context document differs, and verification catches anything wrong. Adding an identity-provider member makes some product the first the broker knows by name, and that should be forced by evidence rather than chosen in advance.
Coverage of the non-interactive path
Both the interactive and the non-interactive sources call the same verification gate. Whatever the measurement selects therefore applies at both grant sites. Applying it at one and not the other would make the same context document behave differently depending on how somebody logged in, which is a defect shaped like a design decision.
This slice additionally adds the first live run of the non-interactive path against any deployment. Today that path is proven only against the in-process fake issuer, on every product.
Secret handling for the live non-interactive run
The client secret never reaches a file. The smoke package carries a fixed constant naming the environment variable, in the same way it already carries a fixed secure-store reference chosen so a live run cannot collide with a real one. The running instructions tell an operator to export the value; the deployment description file names neither the value nor the variable, and carries only a comment pointing at those instructions.
This preserves the harness's written position that a client secret in a casually-shared file is a mistake, and it exercises the production contract exactly as shipped: a context names a variable, the broker reads it into process memory, and the value never reaches shell state, the secure store, or a module's environment.
Documentation placement
The Thunder walkthrough is a separate guide, reached by a single added line in the existing login guide.
The structurally correct placement is a new numbered section beside Asgardeo and Identity Server. That is rejected here for a specific reason: it renumbers six subsequent sections, requiring cross-reference edits inside the login guide and in the smoke harness's running instructions and deployment description — three files under concurrent edit by parallel work on device login and reference-module token tests.
Two supporting reasons make the compromise defensible on its own terms: the login guide already exceeds nine hundred lines and a third walkthrough pushes it past thirteen hundred; and Thunder at
v1.0.0-alpha2will churn far more than the other two, with churn cheapest in a file nothing cross-references.A follow-up issue is filed to split all three product walkthroughs symmetrically once the parallel work has landed.
Walkthrough form
The Thunder walkthrough is a console click-through, matching the genre of the Asgardeo and Identity Server sections. Thunder ships an administrative console, so this is available.
The known cost is that a click-through against an alpha console goes stale faster than a request-based recipe, and when it does a reader cannot distinguish a moved control from a broken deployment. This is mitigated by pinning an explicit Thunder image tag in the container recipe and naming that version in the walkthrough, so a reader can tell which console the steps described.
Facts established against a running Thunder before this spec
latestimage tag currently carries an earlier alpha than the newest release. An explicit tag is pinned; if the newest release has no published image, the spike measures what exists and the walkthrough says so.Testing Decisions
What makes a good test here
A test drives external behaviour through the highest available entry point and asserts on what a caller observes: the grant handed over, or the typed problem refused with. It does not reach into how a grant was obtained. The existing tests in the authentication package are the model — they assert that a session is narrowed to what a module asked for, that a broader grant is refused rather than accepted, that a rotated credential is persisted before a grant is returned, and that no refusal carries session material. None of them names a derivation mechanism.
Live runs are a separate category. They are guarded by a build tag, never execute in the default gate, and are never answered from the test cache — a cached pass would report a deployment as working without having contacted it.
Seams
No new seams. Three existing ones, one extended.
The broker's acquisition entry point, driven against the in-process fake issuer. This is the primary and only seam for new derivation behaviour. If the measurement selects mechanism 2 or 4, Thunder's behaviour is modelled by new options on the fake issuer — the same pattern that already models refresh-grant scope handling, client-credentials scope handling, audience contents, refresh-token rotation, an omitted scope member, loopback port flexibility, and a malformed signing certificate. Assertions go through the fake issuer's introspection helper, which already reports whether a token is active and what permissions and audiences it carries.
The smoke package's deployment configuration and the context document it builds. Untagged and inside the default gate. This is already the seam that proves a live run's document is readable by the shell before any browser opens. The Thunder deployment description, the identity type it implies, and the non-interactive document shape are proven here.
The live runs behind the smoke build tag. Outside the default gate. The existing interactive login run is untouched. Two runs are added: the Thunder experiments, and the first live non-interactive acquisition.
Explicitly not added: an exported derivation-strategy type in the authentication package. The existing unexported source abstraction already answers the question a strategy type would answer, and exporting one so a test could reach it would add a second seam to a package that has one. Any new mechanism is proven through the broker's acquisition entry point.
Modules tested
Prior art
Out of Scope
Further Notes
The outcome that changes the conversation
The largest unknown is not the derivation mechanism. It is whether Thunder has an API-resource-and-scopes model at all comparable to the other two products. A running Thunder advertises only the standard OpenID permission set. Asgardeo and Identity Server both allow registering product permissions against an API resource and issuing them to an application.
If Thunder's model differs enough that the reference module's permissions cannot be registered and issued, then every mechanism in the decision rule fails on the permissions half of verification before audience binding is reached, and this stops being a narrowing question. That outcome is escalated rather than worked around.
Relationship to prior research
resourceparameter on authorization and token requests generally, not on token exchange specifically.Conventions
Typed problems with stable codes and authentication-class exit codes. Conventional commits. Commits GPG-signed. The full deterministic gate — tests, vet, lint, and the architecture-proof acceptance run — passes before pushing.
Concurrency with parallel work
Parallel sessions are working on device login and on reference-module token tests. Expected shared surfaces are the login guide, the smoke deployment description, and the smoke configuration. Edits to all three are additive and localised by design: a single pointer line in the login guide, an appended deployment block, and added constants and fields in the configuration.