Homepage

Roles and Permissions

Last edit: Dec 05, 2025

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.

Developer guide icon

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)

Developer guide icon

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.register
  • admin.manage
  • contacts.list
  • orders.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.

Questions?

We are always happy to help with any questions you may have.

contact us