Building your app

Role permissions inside your app (rbac_permissions)

Scopes say what the platform lets your app read. This block says what your app lets the school's staff do inside it: who sees the attendance page, who approves a request, who exports the report. Permissions created in the organisation and granted to its roles, guarded in your code with can() — like any platform permission.

Shape

{
  "moduleId": "okta-hdor",
  "rbac_permissions": {
    "tenant-admin": [
      "okta_hdor.attendance.view",
      "okta_hdor.attendance.update",
      "okta_hdor.reports.export"
    ],
    "teacher": [
      "okta_hdor.attendance.view",
      "okta_hdor.attendance.update"
    ]
  },
  "rbac_labels": {
    "feature": { "ar": "الحضور", "en": "Attendance" },
    "permissions": {
      "okta_hdor.attendance.view":   { "ar": "عرض الحضور",   "en": "View attendance" },
      "okta_hdor.attendance.update": { "ar": "تسجيل الحضور", "en": "Record attendance" },
      "okta_hdor.reports.export":    { "ar": "تصدير التقارير", "en": "Export reports" }
    }
  }
}
  • Keys are GLOBAL role names on the platform (tenant-admin, teacher, …). A role the platform does not know is skipped silently at install, so do not invent roles.
  • Every name is <feature>.<resource>.<action>, and the feature is your app's moduleId in snake_case: okta-hdor → okta_hdor. The platform groups the organisation's permission editor by the first segment and creates the name exactly as written, so a name outside your group (education.students.delete, say) lands inside the platform's own group and shadows its vocabulary — which is why it is refused by name at sync and never reaches publish. Specific verbs (view, create, update, delete, export, approve), not manage.
  • rbac_labels is what the school sees. The platform composes permission titles from its own language files and shows the raw key for anything it does not know — and your resources are by definition unknown to it. Without this block the school admin sees a group called okta_hdor containing okta_hdor.attendance.view. One title for the group, one per permission, Arabic and English (80 characters max); a title for an undeclared name is dropped.
  • The block is capped at 100 permissions.

How it reaches the version

The block is read from manifest.json on the branch by the version editor's "Sync from manifest" button (or the MCP tool sync_from_manifest), and shown read-only on the "Role permissions" card — with every refused entry and its reason. You can also set it directly with set_rbac_permissions and read it back with get_rbac_permissions, but keep the repository as the source of truth: the code calling can('okta_hdor.attendance.view') lives there, and the two names must match character for character.

Then the checker runs: validate_manifest refuses a name outside your group and warns about a permission with no title before you publish.

When they are created in the organisation

On first install AND on every publish — not at install alone. When you publish a version that declares a new permission, it is created in every installed organisation and granted to its named roles as the update reaches them; the school does not reinstall.

What you declare for a role is ALL that role holds of yours

On every publish the platform reconciles its own roles with your manifest: a role you NAME in rbac_permissions ends up holding what you declared for it and nothing else of yours — so anything an older version declared for that role and you stopped declaring is revoked from it.

  • A role you do not mention is untouched. Silence is not «revoke everything»: an app that never mentioned tenant-admin never claimed anything about it, and reading silence as a revocation would take screens away from whoever runs the school.
  • To empty a role, name it with an empty list: "administrator": []. That is the only sentence that says «this role holds none of my permissions», and your only way to take back a grant an older version made.
  • The school's own groups are never touched («Accounting», «Staff affairs»), nor is a permission handed straight to a person. Those are the school's decision, not yours.
  • And nothing outside your namespace: the reconciliation only ever sees names starting with your own <feature>..

Why it has to exist: the rows that grant a permission to a platform role carry no organisation column — one row is every school at once. So an app that declared sixteen permissions for administrator in an early version and then stopped declaring them left every administrator in every school holding payroll and staff-file editing, with no way for the developer to take it back and nothing in the school saying where it came from.

What the platform CREATED in the organisation stays: the permission remains in the school's list (so it can put it in a group of its own); what is revoked is its grant to the platform role. And name a permission right once — renaming means two live names in every school.

In code

// A Livewire page — the guard in mount(); the menu hides the link by the same name:
protected ?string $permission = 'okta_hdor.attendance.view';

public function mount(): void
{
    abort_unless(auth()->user()?->can($this->permission), 403);
}

php artisan okta-hdor:make-page Attendance --permission=okta_hdor.attendance.view generates the guard and the menu entry together. Hiding a link is not access control; the page is what gets guarded.

What is not here: no scope is requested from the organisation and no consent screen — these are permissions inside your app over your app's data, and the school distributes them across its roles from its roles settings exactly as it does for any platform feature.


View the whole guide on one page · ↧ Raw Markdown