Configure a DXP Provider
A DXP Provider is a Stack9 Core instance where your users log in. When DXP is enabled in provider mode, the navigation bar shows DXP tiles (Email, Marketing, Pages) alongside regular app tiles. Clicking a tile issues a short-lived one-time code and redirects the user to the DXP Client instance — no second login required.
Prerequisites
- Stack9 Core instance running v5.0.0 or later.
- The DXP modules you want to expose must be listed in
Modulesinstack9.config.json(e.g.@april9au/stack9-marketing-module). - A DXP Client instance is already deployed and its URL is known.
- A shared secret string agreed upon between provider and client operators (any random string; minimum 16 characters recommended).
Step 1: Set environment variables
These variables must be present in the APP_SECRETS block (or equivalent env injection) for the Core instance:
| Variable | Required | Description |
|---|---|---|
APP_DXP_CODE_EXCHANGE_SECRET | Yes | Pre-shared secret used by the Client to authenticate server-to-server code exchange. Must match APP_DXP_CODE_EXCHANGE_SECRET on the Client exactly. |
APP_DXP_CODE_TTL_SECONDS | No | Lifetime of one-time codes in seconds. Default: 30. Increase only for testing purposes. |
SERVER_BASE_URL | Yes | The Provider's own public base URL (e.g. https://acme.stack9.com). Included in the exchange payload so the Client can validate the origin and render a "Back to" link. |
APP_S9_APIS_API_KEY | Yes | The tenant's API key for the Stack9 APIs service. Delivered to the Client via code exchange so it can proxy API calls on behalf of the tenant. |
APP_S9_APIS_BASE_URL | Yes | Base URL of the Stack9 APIs service (e.g. https://api.stack9.com). |
AUTH_TENANT_ID | Yes | The organisation/tenant identifier. Included in the code payload as orgId. |
Example APP_SECRETS fragment:
{
"APP_DXP_CODE_EXCHANGE_SECRET": "your-shared-secret-here",
"APP_DXP_CODE_TTL_SECONDS": "30",
"APP_S9_APIS_API_KEY": "sk-...",
"APP_S9_APIS_BASE_URL": "https://api.stack9.com"
}
Step 2: Configure stack9.config.json
Add a dxp block to the instance's stack9.config.json. Set mode to "provider" and list each DXP app you want to expose as a tile.
{
"ProjectName": "acme-core",
"Stack9CoreVersion": "5.0.0",
"Modules": [
"@april9au/stack9-core-module",
"@april9au/stack9-marketing-module",
"@april9au/stack9-email-module",
"@april9au/stack9-pages-module"
],
"dxp": {
"mode": "provider",
"apps": [
{
"appKey": "mkt",
"path": "/mkt"
},
{
"appKey": "email",
"path": "/email"
},
{
"appKey": "pages",
"path": "/pages"
}
]
}
}
dxp block fields
| Field | Type | Description |
|---|---|---|
mode | "provider" | Activates provider mode. The DXP tile redirect endpoints become active. |
apps | array | Each entry represents one DXP app tile. |
apps[].appKey | "email" | "mkt" | "pages" | Identifies the DXP module. Must match a module installed in Modules. |
apps[].path | string | The path on the Client instance to redirect to after sign-in (e.g. /mkt). |
apps[].iconName | string (optional) | Overrides the default icon for this tile. |
apps[].description | string (optional) | Overrides the default tile description. |
Startup validation: The instance will refuse to start if
APP_DXP_CODE_EXCHANGE_SECRETis missing, ifappsis empty, or if anyappKeyreferences a module not inModules.
Step 3: Run bootstrap
Bootstrap provisions the app_permissions records required for DXP tile visibility. Without this step, DXP tiles will not appear in navigation for any user.
node main.js bootstrap
Bootstrap is idempotent — it safe to re-run. It will:
- Create or update an
app_permissionsrow for each DXPappKey. - Set
minimumRole≤ Privileged (3) so tiles are visible to users with that access level.
Permissions are self-managed: You do not need to create App Permissions entries manually in the Stack9 admin UI. Bootstrap creates them automatically.
How it works at runtime
When a user clicks a DXP tile, the Provider's navigation handler calls:
GET /api/auth/dxp/redirect/:appType
This endpoint:
- Generates a cryptographically random 32-byte one-time code.
- Stores the code in Redis alongside the user's identity, roles, API key, and origin URL. The code expires after
DXP_CODE_TTL_SECONDS(default: 30 seconds). - Redirects the browser to the Client:
{clientUrl}/api/auth/dxp/callback?code={code}&origin={providerOrigin}&redirect={path}
sequenceDiagram
participant U as Browser
participant P as Provider
participant R as Redis
U->>P: Click DXP tile (authenticated)
P->>P: Generate one-time code (32-byte hex)
P->>R: Store code payload (TTL: 30s)
P-->>U: HTTP 302 → Client /api/auth/dxp/callback?code=X&origin=Y&redirect=/mkt
Note over U: Browser follows redirect to Client
The Client then performs a server-to-server exchange with the Provider (see the DXP Auth Flow concept page and the Configure DXP Client guide).
Verifying the setup
- Log in to the Provider instance.
- Open the navigation bar — DXP tiles should appear for each configured
appKey. - Click a DXP tile — you should be redirected to the Client and automatically signed in.
- Check the Provider server logs for:
dxp.provider.code.issued— confirms the code was generated and stored.
If tiles do not appear, check:
APP_DXP_CODE_EXCHANGE_SECRETis set and not empty.- Bootstrap has been run after the
dxpconfig block was added. - The
appKeyvalues in config match installed modules.
App permissions and tile visibility
Each DXP tile has an app_permissions row with minimumRole set to Privileged (3) by default. A user must have a role level of at least 3 to see the tile. Administrators can raise the minimumRole in the App Permissions admin panel to restrict access further.
Do not lower minimumRole below the Provider's DXP-granted level — this would have no effect since tile visibility is gated by the minimum, not the user's maximum role.