Roles and Permissions
Up to this point, you have implemented authentication in your example application: users can register, sign in, and sign out. Your layout now adapts automatically based on whether someone is logged in.
The next step is authorization. This determines what an authenticated user is allowed to do. Before we protect the /admin page created earlier, we need to understand how platformOS manages permissions.
The User module uses a flexible Role-Based Access Control (RBAC) system. It is based on three core concepts:
- Profiles (each user has one)
- Roles (assigned to profiles)
- Permissions (bundled inside roles)
Authentication vs. Authorization
Although the terms are often used together, authentication and authorization solve two very different problems.
Authentication: Who are you?
Authentication verifies identity. When a user signs in, platformOS stores their identity in:
{{ context.current_user }}
This object exists only when a valid session cookie is present. You used it earlier to display dynamic navigation.
Authorization: What are you allowed to do?
Authorization determines whether the authenticated user has permission to perform an action.
This object exists only when a valid session cookie is present. You used it earlier to display dynamic navigation.
The User Module provides helpers such as:
function can = 'modules/user/helpers/can_do', requester: current_profile, do: 'admin.view'
This command returns true or false depending on whether the current profile has at least one role that includes the permission admin.view.
You will soon use this helper to protect the /admin page.
For detailed guidance on RBAC, complete with copy-paste examples, refer to the RBAC Authorization section of the User Module README.
Built-In Roles (provided by the User Module)
When you install the User Module, several roles already exist:
| Role | Description |
|---|---|
anonymous |
Assigned automatically when no user is logged in. Used to grant access to pages such as /sessions/new (login), /users/new (registration), and /passwords/reset (request reset link). These pages should not require a logged-in session. |
authenticated |
Assigned to every logged-in user. Useful for pages like a dashboard, profile management, or anything that should only be visible after login. |
admin |
Includes permissions related to administrative management. |
superadmin |
Has full access to everything. This role is typically reserved for high-level operations and should not be modified. |
For learning purposes, you will not use the built-in admin role.
Instead, you’ll create your own custom role so you can see how RBAC works end-to-end.
Preparing Your Own Permissions Override
The User Module defines default permissions internally. Since module files must never be edited directly, your project needs an override.
Create it by copying the permissions file into your app/ directory:
Run:
mkdir -p app/modules/user/public/lib/queries/role_permissions
cp modules/user/public/lib/queries/role_permissions/permissions.liquid \
app/modules/user/public/lib/queries/role_permissions/permissions.liquid
This creates a safe override that allows you to define your own roles and permissions without modifying module files directly.
From this point forward, platformOS will automatically prioritizes your app version of the permission file:
app/modules/user/.../permissions.liquid ← your version (active)
modules/user/.../permissions.liquid ← module version (ignored)
For additional setup details and configuration options, refer to the User Module README on GitHub.
Understanding the Permissions File
Open the newly copied file. It contains a JSON structure describing each role and its permissions.
app/modules/user/public/lib/queries/role_permissions/permissions.liquid
{% parse_json data %}
{
{% if context.constants.USER_DEFAULT_ROLE != blank %}
"{{ context.constants.USER_DEFAULT_ROLE }}": [],
{% endif %}
"anonymous": ["sessions.create", "users.register"],
"authenticated": ["sessions.destroy","oauth.manage"],
"admin": ["admin_pages.view", "admin.users.manage", "users.impersonate"],
"member": ["profile.manage"],
"superadmin": ["users.impersonate_superadmin"]
}
{% endparse_json %}
{% return data %}
Each key is a role name, and each value is an array of permissions. You can now extend this file with your own custom roles.
You can now extend this file with your own custom roles and permissions.
Creating a Custom Role
Let’s add a new role to your project. In this tutorial, we will create a simple role named manager. This role will eventually grant access to your custom admin page.
Tip
Always edit the permissions file inside your app/ directory. Never modify the version inside modules/, as module updates will overwrite those changes.
Permission Naming Convention
Permissions follow a naming convention:
resource.action
The resource identifies the part of the application being controlled.
The action describes what the role is allowed to do with that resource.
For example:
users.registeradmin.managecontacts.listorders.delete
You are free to choose the level of granularity. A common and practical guideline is: if a user can create something, they usually need to edit and delete it as well. This is why broader permissions such as *.manage appear frequently, while overly fine-grained permissions are less common.
For this tutorial, you only need a single permission: admin.view. This will allow the role to access the admin page you created earlier.
Open your overridden permissions file and add a new role:
"manager": ["admin.view"]
Your updated file might look like this:
app/modules/user/public/lib/queries/role_permissions/permissions.liquid
{% parse_json data %}
{
{% if context.constants.USER_DEFAULT_ROLE != blank %}
"{{ context.constants.USER_DEFAULT_ROLE }}": [],
{% endif %}
"anonymous": ["sessions.create", "users.register"],
"authenticated": ["sessions.destroy","oauth.manage"],
"admin": ["admin_pages.view", "admin.users.manage", "users.impersonate"],
"member": ["profile.manage"],
"superadmin": ["users.impersonate_superadmin"],
"manager": ["admin.view"]
}
{% endparse_json %}
{% return data %}
This means anyone with the manager role is allowed to perform the admin.view action. In the next step, you’ll update the page so that only users whose role includes admin.view are allowed to open it.