Register an app
Apps are onboarded through a declarative manifest (app.json / manifest.json) — plugin-style registration on the control plane or the tenant IdP.
| Kind | Where it lives | Who sees it |
|---|---|---|
| Global catalogue | Published once on the control plane (fw-saas-admin) | Available to platforms/tenants (subject to allowlists) |
| Tenant-private | Registered only on that tenant's IdP | Visible only to that tenant |
| Access | Manifest + RBAC | Grant app:{id}:access and fine-grained permissions |
This page is for tenant admins and app developers. For the OIDC code+PKCE wiring itself, see Connect an app.
1. Two places to register
Global catalogue (fw-saas-admin)
- Operator publishes apps once for every platform/tenant.
- Platform allowlists apply only to these catalogue apps (hosted /
deployable). - UI: control plane → Applications → Register from manifest (or form).
- Private tenant apps are not created here.
Tenant-private (fw-saas-tenant IdP)
- Each tenant registers apps only they see (internal tools, private dashboards, …).
- UI: tenant admin console → Apps → Register app (paste or upload
app.json). - Backplane may inspect the tenant DO for support (read); promote private → global is out of scope for now.
2. App kinds — one flag
"deployable": true | false
deployable | Meaning | Typical example |
|---|---|---|
true | Hosted Worker / bundle path (catalogue deploy on WfP) | CRM, ia-chat |
false | External contract only: OAuth client + permissions, no platform deploy | Jarvis, fees on a custom host |
External apps must declare oauth.redirect_uris. Hosted apps may omit OAuth URIs only when the callback lives on the app's own url host (host trust is scoped per app — there is no same-domain bypass; see Connect an app).
3. Manifest (app.json / manifest.json)
MVP fields:
| Field | Required | Notes |
|---|---|---|
id | yes | Slug [a-z][a-z0-9-]{0,62} — also the OAuth client_id |
name | yes | Display name |
description | no | Short blurb |
icon | no | Absolute URL |
version | no | Semver (1.2.3) — used for catalogue update detection; allowed on private too |
deployable | yes* | Default false if omitted |
homepage / support / support_url | no | Links for operators |
oauth.redirect_uris | required if not deployable | Exact callback URLs |
permissions[] | no | { id, label, description? } — app-scoped ids e.g. jarvis.chat.read |
\* Omitting deployable means false (external).
Implicit permission
On register, the IdP always merges:
app:{id}:access
Meaning: may enter this app. Fine permissions are separate. Having only access and zero fine perms is valid — the app decides its UI (user B1 vs B2).
Example — external (Jarvis)
{
"id": "jarvis",
"name": "Jarvis",
"description": "AI assistant (OIDC relying party, not hosted by Fixweb)",
"version": "1.4.0",
"deployable": false,
"homepage": "https://app.fixweb.cloud",
"oauth": {
"redirect_uris": [
"https://app.fixweb.cloud/callback"
]
},
"permissions": [
{ "id": "jarvis.chat.read", "label": "Read chat history" },
{ "id": "jarvis.chat.write", "label": "Send messages" }
]
}
Example — hosted catalogue app
{
"id": "crm",
"name": "CRM",
"version": "2.0.0",
"deployable": true,
"homepage": "https://fixweb.cloud",
"permissions": [
{ "id": "crm.contacts.read", "label": "Read contacts" },
{ "id": "crm.contacts.write", "label": "Edit contacts" }
]
}
4. Evolving a manifest (no reinstall)
You do not destroy or reinstall a deployed app to change its IdP manifest (permissions, OAuth callbacks, homepage, …). Re-register in place.
Catalogue / hosted apps
- Update the manifest on the control plane
(POST /api/admin/applications/register or Applications → Register from manifest). Include the full permissions[] you want as the new catalog.
- Tenants pick up the change without redeploy: the tenant console
re-syncs the catalogue IdP manifest onto the local IdP when Apps / Catalog loads, and again after activate / update.
- Re-grant roles/users/groups if you added new permission ids (existing
grants stay; removed ids are dropped from the catalog and their grants).
Tenant-private apps
Re-POST /api/admin/applications/register (or Apps → Register app) on that tenant IdP with the updated manifest. Same merge rules.
Partial re-register
If the body omits permissions or oauth, those fields are left unchanged on the IdP (hostname / metadata sync cannot wipe the RBAC catalog). An explicit "permissions": [] does replace the fine-grained catalog (app:{id}:access is always kept).
Deploy bindings (app.manifest.json)
Worker resources (KV, secrets, features, vars, …) live on the release, not on the IdP row. To evolve them: publish a new version with the updated deploy manifest, then activate / update — do not destroy the deployment. Versions are immutable (409 version_exists); see the publish contract.
5. Where to click (tenant admin)
- Open the tenant admin console (
https://admin-admin.fixweb.cloud). - Sidebar → Apps.
- Register app → paste or upload the manifest → Register.
- Select the app → Grant to role / user / group → pick permissions (include
app:{id}:accessto enter the app).
Legacy gated grants (Roles → applications) still work alongside permission grants.
6. RBAC — IdP is source of truth
Users, roles, groups, and permissions live on the tenant IdP.
Registering an app merges its permissions (plus app:{id}:access) into the tenant permission catalog.
Admins grant via any combination of:
| Path | API / UI |
|---|---|
| Role default grants | Apps → grant to role, or PUT /api/admin/roles/:id/permissions |
| User direct grants | Apps → grant to user, or PUT /api/admin/users/:id/permissions |
| Group grants | Apps → grant to IdP group (seen at SSO), or PUT /api/admin/group-permissions |
Groups are IdP groups (e.g. Microsoft / Google “Marketing”) — not inventing a second group system.
JWT claims (relying parties)
Access tokens expose under properties:
| Claim | Meaning |
|---|---|
roles | Role ids |
permissions | Effective union (roles + user + group grants) |
apps | Application ids the user may enter |
Verify with JWKS — always:
{issuer}/.well-known/jwks.json
Never pin a kid or PEM. Details: Connect an app · Integration guide.
7. API (tenant IdP admin)
Every route below is mounted under /api/admin on the tenant IdP origin and requires an admin credential — one of:
| Credential | Header | Who uses it |
|---|---|---|
| Admin user access token | Authorization: Bearer <token> | Tenant admins (the console uses this) |
| Bootstrap token | x-admin-token: <ADMIN_BOOTSTRAP_TOKEN> | First deploy / headless seeding |
| Operator grant | x-impersonation-grant: <grant> | Platform operator, minted on the control plane |
POST /api/admin/applications/register
Authorization: Bearer <admin access token>
Content-Type: application/json
{ …manifest… }
Optional extras on the same body: url, access_level, visible, source (private default | catalogue).
GET /api/admin/applications
GET /api/admin/applications/:id
GET /api/admin/applications/:id/grants
GET /api/admin/permissions?application_id=:id
PUT /api/admin/roles/:id/permissions
PUT /api/admin/users/:id/permissions
PUT /api/admin/group-permissions
Control plane catalogue:
POST https://admin.fixweb.cloud/api/admin/applications/register
8. Checklist
- [ ] Manifest
idstable and equals OAuthclient_id - [ ] External →
deployable: false+oauth.redirect_uris - [ ] Hosted →
deployable: trueon the catalogue; platform allowlist includes the app - [ ] Grants include
app:{id}:access(and fine perms as needed) - [ ] Evolving permissions → re-register (catalogue or private); no destroy/reinstall
- [ ] Evolving deploy bindings → new release version + activate
- [ ] RP verifies ES256 via
{issuer}/.well-known/jwks.json - [ ] RP enforces fine permissions from
properties.permissionsin its own UI/API