[Proposal] Federated subscription support for standalone devportal #1747
Closed
Piumal1999
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Problem
The standalone developer portal is designed to host APIs from any gateway — WSO2 Platform gateway, Kong, Apigee, AWS API Gateway, and others. However, the current implementation hardcodes the WSO2 API Platform subscription and API key flow inline across controllers and services. Extending the portal to support a second gateway requires modifying the same logic in multiple places, with no clear boundary between gateway-specific behaviour and generic portal logic.
Each gateway has its own conventions for how a developer obtains access to an API:
Without a clear abstraction, every new gateway adds conditional branches that erode maintainability.
Proposed Solution
Introduce a gateway adapter layer that encapsulates all gateway-specific credential operations behind a narrow, consistent interface. The portal owns the subscription record as a local permission entry; the adapter handles only the gateway-side operations. The existing Platform gateway related implementation becomes the first adapter. New gateways slot in by adding a single folder and a config block — no changes to core portal logic.
Scope
In scope:
keyIssuer.js) for future in-portal key generation, decoupled from the current control-plane delegationOut of scope:
wso2/cloud-gateway): We keep the current implementation as it is as the core concepts are different.Data Model
The portal maintains a local mirror of subscription and key metadata. The gateway remains the source of truth for key values; the portal stores only what it needs to address gateway-side resources in future operations.
DP_API_SUBSCRIPTION (extended)
SUB_IDORG_IDAPI_IDPOLICY_IDCREATED_BYGATEWAY_REF_IDSUB_TOKENAPP_IDDP_GATEWAY_ENVIRONMENT (new)
Stores the gateway-specific configuration per organization. Allows different organizations to point to different gateway instances.
ENV_IDORG_IDGATEWAY_TYPEwso2/api-platform,kong)CONFIGCREATED_ATUPDATED_ATThe adapter's
init(config)method receives the parsed JSON from theCONFIGcolumn for the organisation's registered gateway instance.DP_API_KEY (new)
KEY_IDORG_IDAPI_IDCREATED_BYNAMEGATEWAY_REF_IDEXPIRES_ATREVOKED_ATCREATED_ATThe raw key value is never persisted. It is returned to the caller once for display and must not be logged or stored.
Adapter Interface
Each adapter is a Node.js module in
src/gatewayAdapters/<name>/index.jsthat exports the following contract:Capability detection. The adapter declares no explicit capability flags. The dispatcher introspects method presence at runtime:
typeof adapter.subscribe === 'function'determines whether a gateway is token-based. This is also surfaced to the UI through the API metadata response (hasGatewaySubscription).Contracts:
revokeApiKeyandunsubscribemust be idempotent — a "not found" response from the gateway is treated as success.gatewayKeyIdis the durable handle for all subsequent key operations. If an adapter returns a blankgatewayKeyIdfromgenerateApiKey, the dispatcher rejects the result with a 500 error.apiKeymust not be logged or included inraw.Adapter file layout (by convention):
keyIssuer.jsis the designated swap point for when key generation moves from control-plane delegation to direct in-portal generation.Architecture
Component Overview
flowchart TB UI[Developer Portal UI] subgraph Portal Backend R[Routes\n/gateway-subscriptions\n/gateway-api-keys] SS[subscriptionService\nDispatcher] AKS[apiKeyService\nDispatcher] DTO[APIDTO\nhasGatewaySubscription] L[Adapter Loader] end subgraph Adapters A1[wso2/api-platform] A2[kong ...] AN[future adapters] KI[keyIssuer.js] GD[gatewayDelivery.js] end subgraph Persistence DB1[(DP_API_SUBSCRIPTION)] DB2[(DP_API_KEY)] DB3[(DP_GATEWAY_ENVIRONMENT)] end subgraph External CP[(WSO2 API Platform\nControl Plane)] GW[(External Gateway)] end UI --> R UI --> DTO R --> SS R --> AKS SS --> L SS --> DB1 SS --> DB3 AKS --> L AKS --> DB2 AKS --> DB3 DTO --> L L -. init .-> DB3 L --> A1 L --> A2 L --> AN A1 --> KI A1 --> GD KI --> CP GD --> GWAdapter Boot Sequence
sequenceDiagram participant Boot as Startup participant Loader as Adapter Loader participant FS as gatewayAdapters/ participant Adapter as Adapter Module participant DB as DP_GATEWAY_ENVIRONMENT Boot->>Loader: load(rootConfig, orgId) Loader->>FS: Scan subdirectories for index.js loop Each discovered adapter Loader->>Adapter: require(index.js) Loader->>Adapter: Validate shape (gatewayType, displayName, generateApiKey) Loader->>DB: Query CONFIG for orgId + gatewayType DB-->>Loader: Per-org gateway config Loader->>Adapter: init(orgConfig) Adapter-->>Loader: Ready end Loader-->>Boot: Registry populated (org-specific)Subscription and Key Flows
Token-Based Gateway Flow
Applies when the adapter implements
subscribe. The gateway has its own subscription resource that returns a subscription token. The API key is a separate credential used for runtime invocation and is independent of the subscription lifecycle.sequenceDiagram participant Dev as Developer participant UI participant Disp as Dispatcher participant A as Adapter participant GW as Gateway / Control Plane participant DB as DP_API_SUBSCRIPTION / DP_API_KEY Dev->>UI: Subscribe to API (select plan) UI->>Disp: POST /gateway-subscriptions Disp->>A: subscribe(ctx) A->>GW: Create gateway-side subscription GW-->>A: { subscriptionId, subscriptionToken } A-->>Disp: { gatewayRefId, token } Disp->>DB: INSERT DP_API_SUBSCRIPTION\n(GATEWAY_REF_ID, SUB_TOKEN) Disp-->>UI: { subscriptionId, subscriptionToken } UI-->>Dev: Subscription created Note over Dev,DB: The subscription token is stored and retrievable.<br/>The API key lifecycle is independent. Dev->>UI: Generate API key UI->>Disp: POST /gateway-api-keys/generate Disp->>A: generateApiKey(ctx) A->>GW: Issue API key GW-->>A: { apiKey, gatewayKeyId } A-->>Disp: { apiKey, gatewayKeyId } Disp->>DB: INSERT DP_API_KEY (GATEWAY_REF_ID) Disp-->>UI: { apiKey, keyId } UI-->>Dev: Display key (not stored — copy now)Key-Only Gateway Flow
Applies when the adapter does not implement
subscribe. The gateway has no subscription resource. The portal creates a local permission record and immediately issues a key in a single user action.sequenceDiagram participant Dev as Developer participant UI participant Disp as Dispatcher participant A as Adapter participant GW as Gateway participant DB as DP_API_SUBSCRIPTION / DP_API_KEY Dev->>UI: Subscribe and get API key (single action) UI->>Disp: POST /gateway-subscriptions + POST /gateway-api-keys/generate Disp->>DB: INSERT DP_API_SUBSCRIPTION\n(no GATEWAY_REF_ID) Disp->>A: generateApiKey(ctx) A->>GW: Issue API key GW-->>A: { apiKey, gatewayKeyId } A-->>Disp: { apiKey, gatewayKeyId } Disp->>DB: INSERT DP_API_KEY (GATEWAY_REF_ID) Disp-->>UI: { subscription, apiKey, keyId } UI-->>Dev: Display key (not stored — copy now)Failure Handling
Write operations follow a gateway-first, then persist pattern. If the local database write fails after a successful gateway operation, the dispatcher surfaces a 500 error. The gateway-side resource may be left in an inconsistent state; administrators can reconcile manually using the stored
GATEWAY_REF_ID.For subscriptions, the dispatcher attempts a best-effort
adapter.unsubscribecall before surfacing the error, to minimise orphan gateway-side subscriptions. Rollback errors are logged and suppressed so the original failure is preserved.UI Gateway Capability Discovery
The UI determines whether to render a token-based or key-only flow by reading the
hasGatewaySubscriptionfield included in the API metadata response. This field is computed server-side by looking up the registered adapter for the API'sGATEWAY_TYPEand checking for the presence of asubscribemethod. No additional API call is required.Adding a New Gateway Adapter
Built-in Adapters
For adapters distributed with the portal:
src/gatewayAdapters/<name>/index.jsexporting at minimumgatewayType,displayName, andgenerateApiKey. Addsubscribeandunsubscribeonly if the gateway has its own subscription resource.gateways.<name>:block toconfig.yamlwith the configuration expected by the adapter'sinitfunction.DP_GATEWAY_ENVIRONMENTwithGATEWAY_TYPE = '<name>'andCONFIGcontaining the organisation-specific gateway settings (control plane URL, API credentials, etc.).gatewayType: '<name>'via the existing API metadata REST endpoint.DP_GATEWAY_ENVIRONMENT.Custom Adapters (Docker)
For custom adapters maintained outside the portal codebase:
/app/src/gatewayAdapters/<name>/inside the container.gateways.<name>:block to the mountedconfig.yaml.DP_GATEWAY_ENVIRONMENTfor each organisation.Example Docker Compose mount:
Custom Adapters (Executable)
For deployments using the compiled/packaged executable:
./adapters/<name>/).gateways.<name>:block.DP_GATEWAY_ENVIRONMENTfor each organisation.No changes to core portal logic are required in any deployment model.
Why Applications Are Not Federated
The portal has two distinct credential models:
cloud-gatewayThese models are not variations of the same thing. They differ in lifecycle ownership, credential shape, and the portal concerns they involve. Trying to unify them under a single adapter interface requires the interface to accommodate concepts that are only meaningful to one side — producing an abstraction that fits neither well.
What This Is Not
wso2/cloud-gatewaykeeps its existing controllers unchanged. Only the WSO2 API Platform flow moves behind an adapter in this iteration.GATEWAY_TYPE. Multi-environment replication (one key pushed to several gateway instances) is out of scope.DP_GATEWAY_ENVIRONMENTtable is seeded via direct SQL or the portal's internal API. Administrative UI for managing gateway configurations is future work.Beta Was this translation helpful? Give feedback.
All reactions