Docs

Docs

App Role Groups

App role groups let a tenant admin control which users can access which app. When an app is registered, a SCIM group is auto-created for it. Admins add users to that group; those users see it in their /me response.

The flow at a glance#

code
① Register app → app_id + credentials
② Auto-created: a SCIM group with app_id (the app's "role group")
③ Tenant admin adds users to the group
④ User calls /me → groups array lists their group IDs

1. Register an app#

bash
POST /api/v1/apps/register
Authorization: Bearer <admin_token>
Content-Type: application/json
 
{
  "name": "CRM Sync",
  "app_code": "crm_sync"
}

The response includes app_id, client_id, and client_secret (shown once — store it securely).

Side effect: a SCIM group is auto-created in core_scim_groups:

json
{
  "_id": "grp_019f458b-...",
  "display_name": "CRM Sync",
  "app_id": "app-crm-sync-58d93289",
  "tenant_id": "tnt_global"
}

The app_id field links this group to the app. Ordinary user groups have app_id: null.


2. Add users to the group#

Via the SCIM API (requires control-plane admin access):

bash
PATCH /scim/v2/Groups/{group_id}
Authorization: Bearer <cp_token>
Content-Type: application/json
 
{
  "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
  "Operations": [
    {
      "op": "add",
      "path": "members",
      "value": [{"value": "<user_id>"}]
    }
  ]
}

Or directly in the database:

json
// core_group_members
{ "group_id": "grp_019f458b-...", "user_id": "usr_711484e0-..." }

3. User's /me shows their groups#

When a user logs in, the control plane includes their group IDs in the JWT. The backend's /me endpoint surfaces them:

bash
GET /api/v1/auth/me
Authorization: Bearer <user_token>

Response:

json
{
  "sub": "usr_711484e0-...",
  "email": "tej.gangineni@gmail.com",
  "roles": ["user"],
  "groups": ["grp_019f458b-9c5c-70a3-b53b-0154a5de9dd8"],
  "tenantId": "tnt_global",
  "displayName": "Tej G"
}

The groups array contains the group IDs the user belongs to. To check if a user has access to a specific app, see if the app's role group ID is in their groups.


4. Get full group details by ID#

To resolve a group ID to its full details (name, app_id, members), call:

bash
GET /api/v1/groups/{group_id}
Authorization: Bearer <admin_token>

Access rules:

  • tenant_admin / global_admin — can see any group in their tenant
  • Regular users (user role) — can only see groups they are a member of
  • Non-members get 403 Forbidden

Response:

json
{
  "id": "grp_019f458b-9c5c-70a3-b53b-0154a5de9dd8",
  "display_name": "CRM Sync",
  "app_id": "app-crm-sync-58d93289",
  "tenant_id": "tnt_global",
  "members": ["usr_711484e0-...", "usr_92ab..."],
  "member_details": [
    {
      "user_id": "usr_711484e0-...",
      "email": "tej.gangineni@gmail.com",
      "display_name": "Tej G",
      "roles": ["user"]
    },
    {
      "user_id": "usr_92ab...",
      "email": "anita@company.com",
      "display_name": "Anita K",
      "roles": ["tenant_admin"]
    }
  ]
}
FieldDescription
idThe group's unique ID
display_nameHuman-readable name
app_idThe app this group belongs to (null for ordinary user groups)
tenant_idThe tenant this group belongs to
membersList of user IDs in this group
member_detailsFull details per member — user_id, email, display_name, roles

List all groups#

bash
GET /api/v1/groups
Authorization: Bearer <admin_token>

Returns all groups in the caller's tenant, each with the same shape as above.


5. How the portal uses this#

The portal reads groups from /me to determine which app role-groups a user belongs to. This drives:

  • App access gating — show/hide app sections based on group membership
  • Role-based UI — different views per group

Data model#

CollectionPurpose
core_scim_groupsGroups (with optional app_id for app-scoped groups)
core_group_membersUser → group memberships
core_appsApp registry (app_id, client_id, etc.)

core_scim_groups fields#

FieldTypeNotes
_idstringgrp_<ulid>
tenant_idstringtenant scope
display_namestringgroup/role label
app_idstring (nullable)the app this group belongs to; null for ordinary user groups
created_atintepoch seconds
updated_atintepoch seconds

Key points#

app_id is optional (nullable). Ordinary user groups have app_id: null. Only app-scoped role groups carry the app's id. This is backward compatible — existing groups are unchanged.

  • Multi-role per app = multiple groups with the same app_id. A user in multiple groups gets the union of permissions.
  • groups in /me comes from the JWT (issued by the control plane at login, populated from core_group_members).
  • Auto-creation is best-effort — if group creation fails during app registration, the registration still succeeds (logged as a warning).