Set up WSO2 Identity Server for multiple organizations¶
This tutorial configures a self-hosted WSO2 Identity Server (IS) 7.1 or later as the identity provider (IdP) for an API Portal & MCP Hub in multi-organization mode, and then onboards one organization. Every step uses the Identity Server Console (https://<is-host>:9443/console) and the portal's own UI. Screen and field names are as in WSO2 Identity Server 7.3.
The setup works like this:
- One application, API Portal, is registered in the Identity Server root organization and shared with every organization. Each organization gets its own copy, shown as a shared app in that organization.
- A portal organization maps to one Identity Server organization through the
org_handleclaim in the tokens Identity Server issues for it (step 3 also shows how to key onorg_idinstead). The portal stores that value as the organization's IdP reference ID, and creates the organization itself the first time one of its users signs in. - Two roles,
dp_adminanddp_subscriber, are created once on the root application and appear in every organization automatically. Assigning a user one of them gives that user the portal's administrator or subscriber tier. - Onboarding an organization is then a matter of creating the Identity Server organization, adding its users, and assigning them roles. Its first sign-in to the portal provisions it.
Prerequisites¶
- WSO2 Identity Server 7.1 or later, and a Console login for the root organization's administrator.
- The API Portal & MCP Hub's browser-facing URL. This tutorial uses
https://localhost:9543, and the redirect URLs below must match yours exactly.
Step 1: Create the API Portal application¶
In the root organization's Console:
- Go to Applications > New Application > Traditional Web Application.
- Enter these values:
- Name:
API Portal - Protocol: OpenID Connect
- Authorized redirect URLs:
https://localhost:9543/api-portal/default/callback, the portal'sauth.idp.callback_url
- Name:
- Select Allow sharing with organizations.
- Click Create.
Configure the protocol¶
On the application's Protocol tab:
- Under Allowed grant types, select Code and Refresh Token.
- Under Authorized redirect URLs, also add the portal's
auth.idp.logout_redirect_uri, for examplehttps://localhost:9543/api-portal/logout. - Under Allowed origins, add the portal's origin,
https://localhost:9543. - Under Access Token, set Token type to JWT, and under Access Token Attributes, select
roles. Also selectgroupsif you map groups. - Click Update.
The Client ID and Client secret at the top of this tab go into the portal's auth.idp.client_id and auth.idp.client_secret in step 3.
Release the user attributes¶
Without these attributes, users sign in without a name and role-based authorization fails, because no roles claim reaches the portal.
On the User Attributes tab, select these scopes, and click Update:
- Profile: expand it and select at least First Name, Last Name, and Username
- Roles
The organization claims org_id, org_name, and org_handle need no attribute. Identity Server adds them to every token it issues for an organization, including the root organization.
Share the application¶
On the Shared Access tab:
- Under Sharing Policy, select Share the application with all organizations, and click Save. This covers organizations you create later too.
- Turn on Enable enhanced organization login. Without it, a sign-in to an organization succeeds but carries no
roles, so the portal grants nothing.
Step 2: Create the portal roles¶
The role names must match [api_portal.auth.authorization.portal_roles] in the portal's configuration, which step 3 sets to dp_admin and dp_subscriber.
On the application's Roles tab:
- Leave Role Audience set to Application.
- Click + New Role and create
dp_admin. Repeat fordp_subscriber. - Confirm both roles are listed under Assigned Roles, and click Update.
Because the application is shared, both roles appear in every organization on their own. Under User Management > Roles, they're marked as shared roles with the audience application | API Portal.
To give a root organization user a role—for example, the Identity Server administrator while you test—go to User Management > Roles, open the role, and on its Users tab click Assign User.
Step 3: Configure the API Portal¶
Set [api_portal.auth.idp] to the application's credentials from step 1, and the portal roles to the names from step 2. This example runs the portal in Docker on the same host as Identity Server:
[api_portal.multi_organization]
enabled = true
[api_portal.auth]
mode = "idp"
# The root organization's handle: root users, including the IS administrator,
# belong to the configured organization. See "Map the root organization" below.
idp_org_id = "carbon.super"
[api_portal.auth.claim_mappings]
organization = "org_handle" # organizations are identified by their IS handle
org_name = "org_name"
org_handle = "org_handle" # the IS handle also becomes the portal URL handle
roles = "roles"
[api_portal.auth.idp]
client_id = "<api-portal-client-id>"
client_secret = '{{ env "APIP_AP_AUTH_IDP_CLIENT_SECRET" }}'
audience = "<api-portal-client-id>" # recommended
issuer = "https://localhost:9443/oauth2/token"
scope = "openid profile email roles"
authorization_url = "https://localhost:9443/oauth2/authorize"
token_url = "https://host.docker.internal:9443/oauth2/token"
jwks_url = "https://host.docker.internal:9443/oauth2/jwks"
logout_url = "https://localhost:9443/oidc/logout"
callback_url = "https://localhost:9543/api-portal/default/callback"
logout_redirect_uri = "https://localhost:9543/api-portal/logout"
[api_portal.auth.authorization]
enabled = true
mode = "role"
role_to_scope_mapping = "./resources/role-to-scope-mapping.yaml"
page_role_validation = true
[api_portal.auth.authorization.portal_roles]
admin = "dp_admin"
subscriber = "dp_subscriber"
client_secret reads the client secret from the APIP_AP_AUTH_IDP_CLIENT_SECRET environment variable, and the portal doesn't start while it's unset. With the distribution's Docker Compose file, add it to api-platform.env, which the containers load at start:
Get these values right:
scopeincludesroles. Identity Server 7.3 releases therolesclaim only when therolesscope is requested. Without it, users sign in with no role, and an administrator doesn't see Settings.issueris copied exactly. Open the root organization's discovery document,https://localhost:9443/oauth2/token/.well-known/openid-configuration, and copy itsissuervalue. The portal compares it character for character, so a different host name or a trailing slash rejects every bearer token.- The browser and the container use different hosts.
authorization_urlandlogout_urlare browser redirects, so they use a host name the browser resolves.token_urlandjwks_urlare called by the portal container, which reaches Identity Server throughhost.docker.internal. If you run the portal outside Docker, use one host name for all four.
The organization keying in this example uses the Identity Server organization handle. It keeps the portal's org login hint working: a login from /api-portal/acme/... sends org=acme, which Identity Server routes straight to that organization's login page. To key on the organization's UUID instead, set organization = "org_id" and idp_org_id to the root organization's org_id. With org_id keying, users choose their organization on the Identity Server login page.
Accept tokens from an organization's own token endpoint¶
Scripts and other API clients often get a token straight from an organization's own token endpoint, https://localhost:9443/o/<organization-id>/oauth2/token, for example with the organization's copy of API Portal. Identity Server 7.3 issues those tokens under the organization's own issuer and signs them with the organization's own key. To accept them, add the per-organization issuer to [api_portal.auth.idp]:
[api_portal.auth.idp]
issuer_template = "https://localhost:9443/o/{org}/oauth2/token"
jwks_url_template = "https://host.docker.internal:9443/o/{org}/oauth2/jwks"
template_org_claim = "org_id" # {org} is the org_id; this example keys organizations on org_handle
Like issuer, issuer_template uses the host name Identity Server puts in its tokens. Like jwks_url, jwks_url_template uses the host the container reaches. If you set audience, also add the client ID of each organization's copy of API Portal, shown on that shared app's Protocol tab, because these tokens carry it as their audience. See Accept tokens from per-organization issuers.
Trust the Identity Server certificate¶
The portal verifies Identity Server's TLS certificate on every call to token_url and jwks_url, and it provisions organizations only over verified TLS. Don't turn verification off with NODE_TLS_REJECT_UNAUTHORIZED=0: it disables provisioning as well.
Instead, add Identity Server's certificate with NODE_EXTRA_CA_CERTS, mounted into the container. The certificate must meet two conditions, or Node.js ignores it:
- It's a CA certificate, with the basic constraint
CA:TRUE. Identity Server's default certificate isn't. - Its subject alternative names cover the host name in
token_urlandjwks_url, such ashost.docker.internal. Identity Server's default certificate covers onlylocalhost.
Give Identity Server a certificate that meets both conditions, for example through a dedicated TLS keystore ([keystore.tls] in Identity Server's deployment.toml), and add the same certificate to Identity Server's own client-truststore.p12.
Map the root organization¶
Users of the root organization, including the Identity Server administrator, sign in with organization claims too: on WSO2 Identity Server 7.x, the root organization's org_handle is carbon.super, its org_name is Super, and its org_id is 10084a8d-113f-4211-a0d5-efe36b082211. Set auth.idp_org_id to the value your organization claim carries for it—carbon.super with handle keying—so the portal's configured organization is the root organization.
If you leave idp_org_id unset, the configured organization's IdP reference ID defaults to its handle, which no token carries. The first root user's sign-in then provisions a separate organization named Super, and root users land there instead of in the configured organization.
Confirm the value on your Identity Server by decoding a root user's token, for example with jq -R 'split(".")[1] | gsub("-"; "+") | gsub("_"; "/") | @base64d | fromjson | .org_handle'. The portal applies idp_org_id to the configured organization at startup, so an existing deployment picks up a change on restart, and root users signed in before the change must sign in again.
Retire a provisioned Super organization first
If a Super organization was already provisioned, retire it before that restart. Otherwise, two organizations carry the root organization's ID, and root sign-ins are refused as ambiguous. The portal can't delete organizations, so give the provisioned one an IdP reference ID that no token carries. It keeps its data, and nobody can sign in to it.
Find it first: it's the organization that carries the root organization's claim value, other than the configured organization (default in the query; use your organization.handle). Its handle varies—for example carbon.super, super, or one with a suffix—so look it up rather than assuming it:
SELECT uuid, handle, display_name, idp_ref_id FROM organizations
WHERE idp_ref_id IN ('carbon.super', '10084a8d-113f-4211-a0d5-efe36b082211')
AND handle <> 'default';
Then retire it by the uuid the query returned:
Restart the portal after you change the configuration:
Step 4: Onboard an organization¶
The example organization is acme.
Create the organization¶
In the root organization's Console, go to Organizations > New Organization, and enter these values:
- Organization Name:
acme - Organization Handle:
acme. Set it explicitly: it becomes the organization's portal URL (/api-portal/acme/...), and you can't change it after you create the organization.
Click Create.
Add the organization's users¶
- On the Organizations page, click the switch icon on the organization's row. The header then shows Super / acme, and API Portal is listed on the organization's Applications page as a shared app.
- Go to User Management > Users > Add User, and create the organization's administrator, for example
acmeadmin, and any subscriber users. - Go to User Management > Roles, open
dp_admin, and on its Users tab click Assign User to assign the administrator. Assign subscriber users todp_subscriberthe same way.
Sign in for the first time¶
The organization's administrator opens the configured organization's page, https://localhost:9543/api-portal/default/views/default, selects Login, and chooses the acme organization on the Identity Server login page. The portal creates the organization, with its handle and name from Identity Server, and the administrator lands in it at /api-portal/acme/views/default. The organization's public pages are open from then on, and a login started from them goes straight to acme's login page.
Set up the organization in the portal¶
Signed in as the organization's administrator, set up the organization under Settings, as you would for any organization:
- Key manager integration: an M2M application created in the
acmeorganization gives its applications tokens fromhttps://<is-host>:9443/o/<organization-id>/oauth2/token. If you recreate the Identity Server organization, its ID changes, so update the token endpoint to match. - Subscription plans: the organization starts with the default plans.
- Webhooks: subscribers registered here receive only
acme's events.
The organization's APIs and MCP servers are published through the Management API, with a token issued for one of its administrators—see Publish a provisioned organization's catalog.
Troubleshooting¶
These symptoms are the common ones with Identity Server:
| Symptom | Cause |
|---|---|
| Users sign in with no name, or get no access | The application's user attributes or access token attributes are missing roles or the profile attributes, or scope doesn't include roles |
| An organization administrator signs in but doesn't see Settings | Enable enhanced organization login is off, scope doesn't include roles, or the user isn't assigned dp_admin in their own organization |
The Identity Server administrator lands in an organization named Super |
auth.idp_org_id doesn't match the root organization's claim—see Map the root organization |
Org claim matches more than one organization's idp_ref_id |
Two portal organizations share an Identity Server organization's ID, typically a leftover Super organization |
A new organization's first sign-in gets 403, and the portal logs Refused to provision an organization from an org claim |
The portal doesn't verify Identity Server's certificate—see Trust the Identity Server certificate |
A bearer token is rejected with no applicable key found in the JSON Web Key Set |
The token came from an organization's own token endpoint, .../o/<orgId>/oauth2/token, which Identity Server 7.3 signs with that organization's key. Add the per-organization issuer—see Accept tokens from an organization's own token endpoint |
Related topics¶
- Serve multiple organizations from one API Portal: how the mode resolves and provisions organizations
- Connect an identity provider to the API Portal: the configuration every IdP needs
- Set up Asgardeo as your identity provider: the cloud identity provider with the same organization model