Set up Asgardeo as your identity provider¶
This tutorial walks you through configuring WSO2 Asgardeo as the identity provider for a production API Portal deployment. It's one worked example of the general procedure in Connect an identity provider to the API Portal—read that first if you're integrating a different provider. For how identity provider authentication compares with local authentication, see Authentication in the API Portal & MCP Hub.
The API Portal & MCP Hub uses Asgardeo's sub-organization model: each API Portal organization maps to one Asgardeo sub-organization. A single Asgardeo application, shared across every portal organization, handles login, and each session is scoped to one sub-organization:
- A portal organization's handle is the Asgardeo sub-org handle.
- When a user selects Login, the portal redirects to Asgardeo with
org=<handle>, scoping the authorization to that sub-organization. - Asgardeo issues a JWT whose organization claim identifies the sub-organization. On every authenticated request, the portal verifies this claim matches the organization being accessed.
- Each session is bound to one sub-organization—reaching a different portal organization's protected pages means logging out and back in on that organization.
Prerequisites¶
- An Asgardeo account at console.asgardeo.io
- The API Portal & MCP Hub accessible at a known hostname
Step 1: Set up your organization¶
- Log in to console.asgardeo.io.
- Create or select your root organization.
- If you need multiple tenants, create sub-organizations at
https://console.asgardeo.io/t/<root-org>/app/organizations.
Step 2: Register the API Portal application¶
The API Portal & MCP Hub is a server-side application that can hold a client secret, so register it as a confidential client. A single-page application is a public client and cannot complete the confidential authorization-code exchange the portal relies on.
- In the root organization, go to Applications > New Application.
- Choose Traditional Web Application and name it
API Portal & MCP Hub. -
Under Authorized redirect URLs, add both, replacing
<org-handle>with the sub-organization handle you settle on in step 5:https://<your-domain>/<org-handle>/callback—the login callbackhttps://<your-domain>/<org-handle>—the post-logout redirect (Asgardeo validatespost_logout_redirect_uriagainst this same list)
This single shared URI pair is the only one you register. It matches the
callback_urlandlogout_redirect_uriyou set in step 4—after the callback, the portal uses the session's stored return path to route the user to the correct organization, so no per-organization redirect URLs are needed. 4. Enable Share with all organizations so users in sub-organizations can log in. 5. Under the Protocol tab, set Access Token Type to JWT. 6. Under the Login Flow tab, remove the Username/Password authenticator and add SSO Authentication (organization SSO), which routes each user to their sub-organization's login experience. 7. Under the User Attributes tab, add these attributes to the token:given_name,family_name,email, androles.
Note the client ID and client secret from the Protocol tab. The portal needs both, and the client ID is also used as the audience in the portal configuration.
Step 3: Create the two roles¶
The portal recognizes two personas: whoever administers it, and whoever consumes APIs through it. In the portal's default mode = "role", a token's role names are expanded into dp:* scopes through the portal's own grant table, so Asgardeo only has to emit role names—it never mints a dp:* scope. Use the names the shipped grant table already defines:
| Asgardeo role | Grants in the portal |
|---|---|
dp_admin |
Every action on the organization: content and theme, the API and MCP server catalog, views, labels, subscription plans, key managers, webhook subscribers, and every application and subscription |
dp_subscriber |
The consumer persona: own applications, subscriptions, and keys, plus read access to the catalog |
- Open the API Portal & MCP Hub application you registered in step 2.
- Under the Roles tab, create an application role named
dp_adminand another nameddp_subscriber. - Assign
dp_adminonly to administrators, anddp_subscriberto regular users in each sub-organization that needs access.
Step 4 points both [api_portal.auth.authorization.portal_roles] entries at these same names, so one pair of Asgardeo roles drives both page access and Management API authorization. To change what either role grants, edit the portal's role-to-scope-mapping.yaml—see Choose how privileges reach the token.
Note
Browser login sessions still pass through the per-operation scope check in role mode, which is the gap role mode exists to close: the session's own roles claim is what the portal expands to authorize each Management API request.
Step 4: Configure the API Portal & MCP Hub¶
Update the [api_portal.auth] tables in configs/config.toml:
[api_portal.auth]
mode = "idp"
[api_portal.auth.idp]
name = "Asgardeo"
issuer = "https://api.asgardeo.io/t/<your-tenant>/oauth2/token"
authorization_url = "https://api.asgardeo.io/t/<your-tenant>/oauth2/authorize"
token_url = "https://api.asgardeo.io/t/<your-tenant>/oauth2/token"
user_info_url = "https://api.asgardeo.io/t/<your-tenant>/oauth2/userinfo"
jwks_url = "https://api.asgardeo.io/t/<your-tenant>/oauth2/jwks"
client_id = "<api-portal-app-client-id>"
client_secret = '{{ env "APIP_AP_AUTH_IDP_CLIENT_SECRET" }}'
audience = "<api-portal-app-client-id>" # Asgardeo sets the client ID as the aud claim
callback_url = "https://<your-domain>/<org-handle>/callback"
logout_url = "https://api.asgardeo.io/t/<your-tenant>/oidc/logout"
logout_redirect_uri = "https://<your-domain>/<org-handle>"
scope = "openid profile email roles"
# Which token claim carries each field. Asgardeo B2B puts the sub-org handle in org_name.
[api_portal.auth.claim_mappings]
organization = "org_name"
roles = "roles"
groups = "groups"
# Expands the roles claim into dp:* scopes, and gates pages on the same two role
# names. This section is mode-independent — it is NOT under [api_portal.auth.idp].
[api_portal.auth.authorization]
enabled = true
mode = "role"
role_to_scope_mapping = "/etc/api-portal/role-to-scope-mapping.yaml"
page_role_validation = true
[api_portal.auth.authorization.portal_roles]
admin = "dp_admin"
subscriber = "dp_subscriber"
mode = "idp" selects the identity provider backend and stops the local login form from being used. callback_url must exactly match one of the authorized redirect URLs you registered in step 2. A single callback_url is shared across all portal organizations—after the callback, the portal uses the session's stored return path to redirect the user to the correct organization, so you register only this one URL with Asgardeo.
Replace <org-handle> in callback_url and logout_redirect_uri with the [api_portal.organization] handle from step 5, and keep it identical to the redirect URLs registered in step 2.
Never write the client secret as a literal in config.toml—the {{ env }} placeholder above reads it from an environment variable instead, so it never has to be committed to source control:
In a production deployment, prefer supplying it from a mounted secret file instead, by swapping the token for '{{ file "/secrets/api-portal/oidc_client_secret" }}' and mounting the secret at that path—resolution fails closed, so a missing or unreadable file aborts startup rather than falling back to an empty credential.
Step 5: Align the portal handle with the sub-organization¶
Asgardeo puts the sub-organization's handle in the org_name claim, and the portal resolves that claim against the organization it serves. The two names have to agree, so set [api_portal.organization] handle to the Asgardeo sub-org's handle—the URL slug shown in the Asgardeo console:
Set this before the portal first starts. The handle is what the portal writes into the organization's IDP reference ID when it seeds the organization row, and that field is fixed afterward—the Management API rejects a request that changes it. Changing the handle later means seeding a new organization.
The handle is also the URL slug in /{handle}/views/{viewName}, so it appears in every portal URL. The portal normalizes it to lowercase, though the claim match itself tolerates any case.
With the two aligned, the login flow closes:
- A user selects Login, and the portal appends
org=<handle>to the Asgardeo authorization URL. - Asgardeo scopes the login session to that sub-organization, and issues a token whose
org_nameclaim identifies it. - On every authenticated request, the portal resolves that claim against the organization it serves. A token minted for a different sub-organization resolves elsewhere and is refused with
403.
Two consequences worth knowing:
- Public pages (the API catalog and documentation) stay accessible without authentication.
- Protected pages (applications, subscriptions, API keys) need a token whose
org_namematches, so a user reaching a different portal organization has to log out and log in again there.
If the handle can't match
When the sub-org handle isn't a name you want in your portal URLs, the alternative is to have Asgardeo emit a separate claim carrying the portal handle as a constant, and map organization to that claim instead. Doing so drops the sub-organization distinction from the check—every sub-org's token then carries the same value—so choose it only for a tenant with a single sub-organization. See when your IdP has no organization concept.
Step 6: Restart and verify¶
Restart the portal so it reloads the configuration:
Open the portal and select Login. Instead of the built-in username and password form, you're redirected to the Asgardeo-hosted login page, and land back on the page you started from after signing in. Signing in as a dp_admin user also reveals the Settings area.
Claim flow summary¶
The Asgardeo token carries these claims through to the API Portal & MCP Hub:
| Claim | Purpose | Configured as |
|---|---|---|
sub |
User identity | Read under a fixed name, not configurable |
org_name |
Sub-organization handle, resolved against the portal's [api_portal.organization] handle |
organization in [api_portal.auth.claim_mappings] |
roles |
Role list. Expanded into Management API scopes through role_to_scope_mapping, and matched against [api_portal.auth.authorization.portal_roles] for page access |
roles in [api_portal.auth.claim_mappings] |
Keep the claim names consistent between the Asgardeo token attributes and the [api_portal.auth.claim_mappings] table.
Two retired keys abort startup
Earlier versions configured roles under [api_portal.auth.idp.roles], with a third super_admin tier. That section is retired, along with auth.role_validation, and leaving either in config.toml fails startup rather than silently applying a default. Use [api_portal.auth.authorization.portal_roles] and auth.authorization.page_role_validation instead—see Authorization.
Alternative: let Asgardeo mint the dp:* scopes¶
mode = "role" above needs no scope registration in Asgardeo, which is why this tutorial uses it. If you'd rather have Asgardeo issue the portal's dp:* scopes directly and set mode = "scope", register them in your tenant first. A helper script does the registration through Asgardeo's own management APIs.
- Create a new OIDC application in Asgardeo, for example named
DevPortal System. - Under API Authorization, add the API Resource Management API and the Application Management API.
- Note its client ID and client secret.
-
Download the script and run it:
curl -sLO https://raw.githubusercontent.com/wso2/api-platform/main/portals/api-portal/production/scripts/register_asgardeo_scopes.sh chmod +x register_asgardeo_scopes.sh ASGARDEO_TENANT=<your-tenant> \ ASGARDEO_CLIENT_ID=<system-app-client-id> \ ASGARDEO_CLIENT_SECRET=<system-app-client-secret> \ ASGARDEO_RESOURCE_IDENTIFIER=https://<your-domain> \ ./register_asgardeo_scopes.sh -
Open the API Portal & MCP Hub application, and under API Authorization add the API resource the script created.
- Assign the
dp:*scopes to your roles, giving administrators the full set and subscribers only what everyday consumer operations need—dp:application:manage,dp:subscription:manage,dp:api_key:manage, and thedp:*:readscopes for browsing the catalog. - Set
mode = "scope"in[api_portal.auth.authorization].
The script registers an API resource representing the portal, with all dp:* scopes under it. For local testing, its default ASGARDEO_RESOURCE_IDENTIFIER=https://localhost:9543 works unchanged. The system application is only needed to run the script, and can be deleted afterward.
In scope mode, browser sessions are preauthorized: the portal skips the per-operation scope check for a user signed in through Asgardeo, and page role gating is the authorization that applies to them. The dp:* scopes then govern machine clients calling /api/v0.9 with a Bearer token.
Related topics¶
- Connect an identity provider to the API Portal: the general procedure this tutorial is one example of
- Authentication in the API Portal & MCP Hub: how identity provider authentication compares with local authentication
- Get a bearer token via curl: calling the Management API with a token from your IdP
- Configurations: every
config.tomlkey