Tenant Config (Control Plane)
The tenant control plane config lets platform operators customize the Steward experience for each tenant — which policies are editable, what the UI looks like, which features are enabled, and how approvals work. This is the layer between a raw multi-tenant Steward deployment and a white-labeled platform-specific experience. Base path:GET/PUT /tenants/:id/configAuth: Tenant-level (
X-Steward-Key)
Get Tenant Config
App Clients And Environments
Steward supports tenant-scoped app clients for grouping local, preview, staging, and production integration settings. App-client mutations require an owner/admin session with recent MFA because they change auth and browser allowlists.client_id or clientId. When supplied, Steward validates redirect_uri against that enabled app client’s redirects instead of the tenant-global fallback and applies that client’s OAuth login-method overrides.
Native app clients should also pin platform identifiers with allowedBundleIds
for iOS and allowedPackageNames for Android. Steward rejects wildcards,
single-label values, and malformed identifiers before persistence, so native
callback trust must be expressed as explicit bundle/package allowlists.
When a native integration supplies X-Steward-Native-Bundle-Id or
X-Steward-Native-Package-Name on app-client-bound auth requests, Steward also
accepts equivalent JSON fields: native_bundle_id, native_package_name,
nativeBundleId, and nativePackageName. It validates identifier syntax and
rejects values that are not allowlisted on the enabled app client. For OAuth
device authorization, the accepted native identifier is bound to the issued
device code and must match again during token polling. Mismatches return
invalid_client and do not mint a session.
These headers and JSON fields provide deterministic server-side allowlist
enforcement. They do not prove the caller is an untampered native binary; real
device attestation, signed app claims, and anti-tamper guarantees must come from
the native platform or an attestation provider.
App Client Secrets
App clients can also have backend-only app secrets for Privy-style REST API authentication and HMAC request signing. The app ID is stable and public in the form<tenantId>/<clientId>. The app secret is returned only once when rotated or created.
Use Basic Auth with the app ID as the username and the app secret as the password, plus X-Steward-App-Id:
requestSigningSecret. Steward validates the Basic auth secret and then checks
X-Steward-Signature against that tenant-scoped secret, so production
deployments do not need a separate global signing secret for app-client server
integrations.
Request Signing Keys
Tenants can also manage standalone encrypted request-signing keys for server integrations that use tenant API keys, platform keys, or delegated signer credentials instead of app-client Basic auth.POST returns
signingSecret once, stores the secret encrypted with the Steward keystore, and
marks previous active keys as retiring for a seven-day overlap. GET returns
metadata only.
Security Checklist
Tenant admins can read a production-readiness checklist for API headers, HSTS, request freshness, HMAC request signatures, browser allowlists, production app clients, and dashboard CSP coverage.Idempotency Metrics
Tenant admins can inspect privacy-preserving idempotency counters for replay and conflict triage:observed, completed, replayed, conflicts, and store_errors. It does not
include idempotency keys, request bodies, replayed responses, or credentials.
POST rotates the secret, returns appSecret once, and marks the previous active secret as retiring for a short overlap window. GET returns metadata only: secret prefix, status, timestamps, and the public app ID.
OIDC / JWT Login Providers
Tenant OIDC providers support direct JWT exchange and enterprise authorization-code SSO. The authorization-code fields are optional; when present, Steward acts as the relying party, verifies the returned ID token, and redirects the app with a short-lived exchange code instead of putting Steward tokens in the URL.viewer. Steward then reuses POST /auth/oauth/exchange for the app-bound one-time code exchange.
SSO Email Domains
Tenant admins can register verified email domains for dashboard/team SSO discovery. This is the domain-routing layer used before SAML/OIDC redirect flows.verificationToken. Publish it as an exact TXT value at _steward-sso.example.com, then call verify. Once verified, public discovery can route email addresses to the tenant:
Team Invitations
Invite-only tenants can create pending team invitations without creating a membership up front. Invitation tokens are returned once and stored hashed.{ invitation, token, emailSent }. Steward stores only the token hash and can send the invitation through the tenant email provider when sendEmail is true. If you do not send through Steward, deliver token through your own email channel. The user accepts it from a personal session with a verified matching email. Invite-only tenants must use POST /user/me/tenants/:tenantId/invitations/accept with the one-time token; email-matched invites are not accepted without the token. Invitation roles can be admin, developer, billing, viewer, or member; owner cannot be granted by invitation.
SAML Dashboard SSO
Tenant admins can configure one SAML 2.0 IdP for dashboard/team SSO. The SAML configuration is separate from domain verification: verified SSO domains decide which tenant owns an email domain, while SAML metadata and ACS routes define the tenant’s service-provider integration.GET /tenants/:id/saml-sso returns the current config, if any, plus generated service-provider values:
PUT /tenants/:id/saml-sso requires an owner/admin session with recent MFA and stores parsed IdP settings:
https URL, certificate entries are PEM certificates without private key material, at most five IdP certs are configured, and JIT-created SSO memberships can only default to viewer unless a SAML group maps the new user to admin, developer, billing, viewer, or member. SAML group mappings cannot grant owner. The metadata route emits unsigned SP metadata with WantAssertionsSigned=true and stable Entity ID/ACS URLs derived from APP_URL, not the request host.
The SP-initiated login route mirrors OIDC auth-code SSO and requires app-side PKCE:
RelayState and InResponseTo request ID, redirects to the tenant IdP, then the ACS route atomically consumes the RelayState and validates the signed SAML response/assertion against the tenant IdP certs, SP Entity ID, ACS URL, request ID, assertion timestamps, configured email attribute, verified email domain, group attributes, and assertion replay table. Successful ACS returns the app to redirect_uri with a short-lived exchange code; the app redeems it through POST /auth/oauth/exchange.
Client IDs must match ^[a-z0-9][a-z0-9_-]{2,63}$. Redirect URLs must be exact https URLs, except loopback development URLs. Origins must be exact origins; app clients cannot use wildcard origins.
MFA Policy
authAbuseConfig.mfa controls tenant-level step-up behavior for sensitive custody operations:
maxAgeSeconds is bounded from 30 seconds to 1 hour and acts as the default freshness window. maxAgeFor can override that window per sensitive surface with the same bounds; omitted actions fall back to maxAgeSeconds. Each requireFor flag defaults to true; setting a flag to false disables only that action’s MFA freshness gate. disableFor.keyImport and disableFor.keyExport default to false; setting either to true blocks private-key import or export at the tenant-policy layer even when the corresponding environment break-glass flags are enabled and the caller has fresh MFA. When signer or key-quorum automation is disabled, vault signing routes require an owner/admin session with recent MFA instead of signer-bound API credentials. Vault signing, key import/export, user-wallet import/export, and tenant-admin control-plane routes use the tenant MFA freshness window and per-action requireFor flags. TOTP, SMS, recovery-code, and passkey MFA step-up sessions can satisfy recent-MFA checks; passkey step-up reuses the signed-in user’s enrolled WebAuthn credentials.
Update Tenant Config
Policy Exposure
policyExposure controls which policy types end users can see and edit in the <PolicyControls> component:
| Value | Behavior |
|---|---|
"visible" | Policy is shown and editable |
"enforced" | Policy is applied but hidden from end users |
"hidden" | Policy is not shown and not automatically applied |
Policy Templates
Policy templates appear in the<PolicyControls> component as one-click starting points. Define them with customizable fields so users can tune limits without editing raw policy configs:
Secret Route Presets
Pre-configured route templates shown in the secret manager UI. Saves users from manually entering host/inject details for common APIs:provisioning: "platform" means the platform provides the credential. provisioning: "user" means the end user provides their own key.
Approval Config
Controls how the approval workflow behaves for this tenant:| Field | Description |
|---|---|
autoExpireSeconds | How long pending approvals last before auto-expiry (0 = never) |
approvers.mode | Who can approve: "owner" (tenant admins), "tenant-admin" |
webhookCallbackEnabled | Whether to fire tx.pending webhooks for this tenant |
Feature Flags
Toggle UI features in@stwd/react components:
| Flag | Default | Description |
|---|---|---|
showFundingQR | true | Show QR code in <WalletOverview> |
showTransactionHistory | true | Enable <TransactionHistory> component |
showSpendDashboard | true | Enable <SpendDashboard> component |
showPolicyControls | true | Enable <PolicyControls> component |
showApprovalQueue | true | Enable <ApprovalQueue> component |
showSecretManager | false | Show secret management UI |
enableSolana | true | Show Solana address alongside EVM |
showChainSelector | false | Show chain-switching UI |
allowAddressExport | true | Allow copying/exporting wallet addresses |
Theme Config
Full color and typography control:@stwd/react. Any fields not provided fall back to the component library defaults.
Theme updates are validated before persistence: color tokens must be exact 6-digit hex values, borderRadius must be between 0 and 32, colorScheme must be light, dark, or system, and fontFamily is restricted to common CSS font-family characters.
logoUrl and faviconUrl must be absolute HTTPS URLs. Local development URLs using http://localhost, http://127.0.0.1, or *.localhost are accepted; javascript:, data:, blob:, file:, relative URLs, protocol-relative URLs, non-local HTTP URLs, credentials in URLs, and values over 2048 characters are rejected. Logos must end in .png; favicons must end in .ico or .png. Submit an empty string to clear a saved asset URL.
Related
- React Components —
@stwd/reactconsumes tenant config automatically - Approvals — Configured by
approvalConfig - Secrets Guide — Configured by
secretRoutePresets - Webhooks — Configured by
webhookCallbackEnabled