feat(apim): bootstrap registers the identity provider and public federated client, with iam creating the federation client
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 0
- Forks
- 3
- Avg merge
- 8h 43m
- Merged PRs (30d)
- 60
Description
Problem Statement
After wso2 iam connect and wso2 iam bootstrap, a developer who wants API Manager on the same login has to register, by hand, everything API Manager needs to federate to ThunderID: a confidential client on ThunderID for API Manager to sign in through, an identity provider on API Manager pointing at it with claim and role mappings and just-in-time provisioning, and a public PKCE client on API Manager with the shell's four loopback callbacks whose authentication step is that identity provider. docs/guides/one-login-thunder-apim.md section 3 lists the steps; they are console clicks or SOAP admin calls, each with a value that must match another (the resource parameter, the groups claim, enabled=true on the default authenticator), and a mistake in any one of them shows up later as auth.credential_unavailable, a second password prompt, or every command refused with auth.narrowing_unavailable. wso2 apim bootstrap today registers only a confidential client through dynamic client registration, which is right for a pipeline and wrong for a browser identity; its next line has to explain the difference. wso2 apim connect --client-id then needs an id the developer copies from the console. Measured in the 2026-09-07 exercise 4 tryout (cli-exercise-4/FINDINGS.md, remaining issue 2) and recorded in docs/research/2026-09-06-single-login-spikes.md section 1.
Solution
Two commands, one per product, because each product's secrets stay inside its own module and the shell brokers a session for one namespace at a time.
wso2 iam apps create apim-federation --type federation --for https://localhost:9443 creates on ThunderID the confidential client API Manager federates through: on the console authentication flow so its sign-on is the one the CLI's login establishes, redirect <for>/commonauth, grants authorization code and refresh, client_secret_post, ID token attributes and scope claims for email, groups and name. The secret is shown once, and the next line names the variable to export and the bootstrap to run.
WSO2_APIM_ADMIN_PASSWORD=… WSO2_APIM_FEDERATION_CLIENT_SECRET=… wso2 apim bootstrap --url https://localhost:9443 --login-provider http://localhost:8492 --federation-client-id apim-federation writes on API Manager, idempotently: the identity provider for that login provider (OpenID Connect federated authenticator, endpoints, callback, the scope and resource query parameters, claim mappings, the group-to-role mapping, just-in-time provisioning on), and the public client wso2-cli-sso (four loopback callbacks, public, PKCE S256 mandatory, JWT tokens, one federated authentication step through that identity provider, consent skipped). It prints wso2 apim connect https://localhost:9443 --client-id <public client id> as the next line. Without --login-provider it does what it does today, for pipelines.
The guide's section 3 becomes those two commands. The user never opens the API Manager console.
User Stories
- As a developer, I want
wso2 iam apps create apim-federation --type federation --for https://localhost:9443to create the confidential client API Manager needs on ThunderID, so that I do not build it in the console. - As a developer, I want that client on the same authentication flow as the CLI's own client, so that one sign-on answers both.
- As a developer, I want the generated secret shown once in the result, and the next line to say
export WSO2_APIM_FEDERATION_CLIENT_SECRET=<the secret above>; then wso2 apim bootstrap --url <for> --login-provider <issuer> --federation-client-id apim-federation, so that I know exactly what to run next without the secret appearing on that line. - As a developer, I want running the same
apps create --type federationagain to reportcreated falseand change nothing, so that reruns are safe. - As a developer, I want
--type federationwithout--forrefused withiam.missing_flag, so that the redirect is never guessed. - As a developer, I want
wso2 apim bootstrap --url <base> --login-provider <issuer>to create the identity provider on API Manager with the OpenID Connect federated authenticator, so that API Manager signs users in at ThunderID. - As a developer, I want the identity provider named after the login provider's host unless
--identity-provider <name>says otherwise, so that two deployments do not collide and I can adopt an existing name. - As a developer, I want the authorization endpoint to be the login provider URL the browser reaches and the token and userinfo endpoints to be
--login-provider-internal-urlwhen given, so that API Manager in a container reaches ThunderID on the host. - As a developer, I want the authenticator's additional query parameters to carry
scope=openid email groupsandresource=<base>/oauth2/token, so that ThunderID accepts the authorization and the ID token carries the groups claim. - As a developer, I want the identity provider to map
groupsto the local role claim andemailto the email claim, and to map a ThunderID group to an API Manager role with--map-group Administrators=admin(repeatable, that pair by default), so that a signed-in administrator gets theapim:*scopes. - As a developer, I want just-in-time provisioning on, silent, to the primary user store, so that mapped roles reach the scope issuer.
- As a developer, I want an existing identity provider of that name updated in place with its default authenticator kept enabled, so that a rerun after a service provider references it does not fail with "Error in disabling default federated authenticator".
- As a developer, I want the public client
wso2-cli-sso(name from--public-client-name) registered with the four loopback callbacks, made public, PKCE S256 mandatory, JWT token type, and its authentication set to one federated step through the identity provider with consent skipped, so that the shell's browser login works as a public client. - As a developer, I want a public client that already exists to be reused and brought to that configuration, reported
created false, so that reruns converge. - As a developer, I want bootstrap's result to show the identity provider name, the public client id, and what was created versus already present, and its next line to be
wso2 apim connect <base> --client-id <public client id>, so that the value connect needs is on screen. - As a developer, I want the confidential pipeline client bootstrap registers today to still be registered on the same run and reported in its own row, so that a deployment set up once serves both people and pipelines.
- As a developer, I want a missing
WSO2_APIM_FEDERATION_CLIENT_SECRETor--federation-client-idrefused withapim.missing_flagnaming thewso2 iam apps create --type federationline, so that the order of the two commands is discoverable. - As a developer, I want API Manager's refusals surfaced with their body under
apim.refused, so that a wrong admin password or a locked-down deployment is diagnosable. - As a developer, I want the certificate refusal
apim.certificate_untrustedfrom bootstrap whenWSO2_CA_FILEis not set, so that the first failure names the fix. - As a developer, I want
wso2 apim bootstrapwithout--login-providerunchanged, so that pipelines keep their path. - As a developer, I want the guide's section 3 replaced by the two commands and their outputs, so that the whole journey is CLI commands.
- As a pipeline author, I want none of this to require a browser, so that it can run from a setup job with the two passwords in the secret store.
- As a maintainer, I want every write to be read-before-write and idempotent, and no secret to appear in any next line or log, so that the module keeps its contract.
Implementation Decisions
- Vocabulary: the ThunderID client is the federation client; the API Manager client the shell logs in with is the public client; the object on API Manager that points at ThunderID is the identity provider. These are API Manager's and ThunderID's own words; CONTEXT.md gains none.
- iam module:
apps creategains--type federationbesidem2mandpublic, with--for <url>(the product it federates: redirect<for>/commonauth). It writes acustomapplication on the console flow family,client_secret_post, grantsauthorization_codeandrefresh_token, ID token user attributesemail,groups,name, scope claimsemailandgroups, a generated secret. The result row shows the secret once; the next line names the variable, never the value. Idempotent by client id. - apim module:
bootstrapgains--login-provider <issuer>,--login-provider-internal-url <url>(default: the issuer),--federation-client-id <id>,--identity-provider <name>(default: the issuer host with the port, dots and colons replaced by hyphens, prefixedwso2-cli-),--public-client-name <name>(defaultwso2-cli-sso),--map-group <group>=<role>repeatable (defaultAdministrators=admin). It readsWSO2_APIM_ADMIN_PASSWORDas today andWSO2_APIM_FEDERATION_CLIENT_SECRET. - API Manager writes go through the admin services the spike measured, with the administrator's basic authentication: dynamic client registration for the public client's creation;
OAuthAdminServiceto make it public, PKCE mandatory, JWT;IdentityApplicationManagementServiceto set the federated authentication step;IdentityProviderMgtServiceto add or update the identity provider. Each is read first and written only when the read differs. The SOAP request and response shapes live in the apim module's internal client beside the existing JSON client; no new dependency. - The identity provider carries: the OpenID Connect federated authenticator with the federation client id and secret, authorization endpoint at the login provider, token and userinfo endpoints at the internal URL, callback
<base>/commonauth,commonAuthQueryParamsscope=openid email groups&resource=<base>/oauth2/token; claim mappingsgroupsto the role claim andemailto the email claim,subas the user claim; role mappings from--map-group; JIT provisioning enabled, silent,PRIMARY; the default authenticator entryenabled=trueon update. - The result gains rows:
Identity provider <name> (created|present),Public client <id> (created|present), alongside today's confidential client row. The next line is thewso2 apim connect <base> --client-id <public client id>line; the pipeline connect line moves to a second sentence. - The apim descriptor is unchanged: the public client id stays per deployment and reaches the shell through
connect --client-id. - Documentation: the guide's section 3 becomes the two commands; the commands reference rows for
iam apps createandapim bootstrap; the module READMEs; the target experience document's bootstrap line; the spikes document gains one line saying which steps bootstrap now applies.
Testing Decisions
- A good test drives the command through the module's handler with the fake deployment and asserts on the result rows, the problem code and recovery, and the requests the fake recorded, never on internal helpers.
- iam module:
modules/iam/cmd/wso2-module-iam/management_test.gofake ThunderID (prior art:TestAppsCreate…,TestRoleAssignResolvesNamesAndIsIdempotent) covers the federation client's body, the secret shown once and absent from the next line, idempotence, and the--forrefusal. - apim module:
modules/apim/cmd/wso2-module-apim/commands_test.gowithfake_test.go(prior art:TestBootstrap…,TestKeyManagersAdd…) gains the four admin services as recorded XML or JSON exchanges from the spike; cases: fresh deployment creates all three, rerun creates none, existing identity provider updated with the enabled flag, missing secret and missing client id refusals,--map-groupin the identity provider body, no--login-providerunchanged, certificate refusal. - Live: one run against
cli-apimandcli-thunder3from~/dev/wso2/cli-exercise-4under a fresh identity provider name and public client name, thenapim connect,login,apis list; recorded as a row in the live matrix.
Out of Scope
- Registering the key manager for the gateway;
wso2 apim key-managers addexists. - Identity Server or Asgardeo as the login provider; the identity provider shape is OpenID Connect and the derivation is measured on ThunderID only.
- Removing what bootstrap wrote; no
unbootstrap. - The confidential pipeline client's shape; unchanged.
- Any change to the shell, the descriptor or
connect.
Further Notes
- Why two commands: ThunderID's system permission and API Manager's administrator password belong to different modules, and the shell brokers one namespace per invocation. A single command would need the apim module to hold a ThunderID token, which ADR 0005 forbids. The one value that crosses is the federation client's secret, in an environment variable, the way every other secret reaches a module.
- Why SOAP: API Manager 4.7.0 exposes no REST API for a service provider's authentication steps or for the identity provider's federated authenticator; the spike used
OAuthAdminService,IdentityApplicationManagementServiceandIdentityProviderMgtServicewith basic authentication and they are what bootstrap applies. - Related: #163 (gateway record), the one-login guide section 3,
docs/research/2026-09-06-single-login-spikes.mdsection 1, exercise 4 findings remaining issue 2.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the existing handlers and tests in modules/iam/cmd/wso2-module-iam/management_test.go and modules/apim/cmd/wso2-module-apim/commands_test.go, including fake_test.go and the existing TestAppsCreate… and TestBootstrap… cases. Read the module clients and the referenced guide and spike documents first. Done means both commands, idempotent API Manager and ThunderID setup, required refusal cases, recorded service exchanges, and the section 3 documentation are covered.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- api, cli, documentation
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 35/100