Insites Docs Developers guide Data and UsersAdmin Role Instead of a Shared Password

Admin Role Instead of a Shared Password

Last updated on October 10, 2026.

Replace a shared admin password with named admin accounts, a role and authorization policies.

A shared admin password is one secret that many people know. You cannot remove one person, and you cannot tell who did what. On Insites you replace it with admin users. Each admin signs in as themselves. A role on their user says what they may manage. An authorization policy on every admin page checks that role before the page runs.

What You Need Before You Start

Note: This is about the admin area of your own site. It is separate from the Instance Admin, which checks its own administrator records.

How It Fits Together

  • A user per admin. Each person has their own user record and signs in with their own email.
  • A role. Roles are stored on the user record as an array property named roles, such as orders_admin. One user can hold several roles.
  • A policy. An authorization policy is a small Liquid file that prints true or false. A page lists its policies in its front matter. The policies run before the page body, so a refused request never reaches your page code.

Step 1: Declare the Roles Property

Add roles to your users as an array property. If your instance already has app/user.yml, add the property to it.

app/user.yml

properties:
  - name: roles
    type: array

Note: Without this file the role check matches nobody and fails without a sound. A query that filters on roles answers with the GraphQL error array_contains only works for arrays, but a Liquid page does not raise it. Every user looks like they have no role, so every admin page refuses everyone, and no Liquid error tells you why.

Step 2: Add Two GraphQL Files

The property name is passed in as a variable, not written into the query.

app/graphql/users/current.graphql

query current($id: ID!, $field: String!) {
  users(per_page: 1, filter: { id: { value: $id } }) {
    results {
      id
      email
      roles: property_array(name: $field)
    }
  }
}

app/graphql/users/set_roles.graphql

mutation set_roles($id: ID!, $field: String!, $roles: [String]) {
  user_update(id: $id, user: { properties: [{ name: $field, value_array: $roles }] }) {
    id
  }
}

Step 3: Give Each Admin a Role

A migration is a good place to do this once. A pending migration runs on deploy, and a migration that has run does not run again. Create one with insites-cli migrations generate <environment> grant_admin_roles, then fill it in.

app/migrations/20261009120000_grant_admin_roles.liquid

{% liquid
  graphql found = 'users/find_by_email', email: 'ana@example.com'
  assign user = found.users.results.first
  assign admin_roles = 'orders_admin' | split: ','
  if user
    graphql saved = 'users/set_roles', id: user.id, field: 'roles', roles: admin_roles
  endif
%}

Build the list of roles with split, as shown. In testing, a list written inline as a tag argument, such as roles: ['orders_admin'], failed to deploy.

users/find_by_email is a query you add that filters users by email: { value: $email }. Repeat the block for each admin.

Note: To add one role to a user without sending their other roles again, use array_append in place of value_array.

Step 4: Write a Policy

Every path through the policy must print true or false. Never let a path print nothing. Check that a user is signed in before you read anything about them.

app/authorization_policies/is_orders_admin.liquid

---
name: is_orders_admin
redirect_to: sign-in
flash_alert: Please sign in as an administrator.
---
{%- liquid
  if context.current_user == blank
    echo false
    return
  endif

  graphql g = 'users/current', id: context.current_user.id, field: 'roles'
  assign profile = g.users.results.first
  if profile == blank
    echo false
    return
  endif

  if profile.roles contains 'orders_admin'
    echo true
  else
    echo false
  endif
-%}

When the policy prints false, the visitor is sent to the redirect_to page with the flash_alert message.

Step 5: Put the Policy on Every Admin Page

List the policy by its name in the front matter of each admin page. A page with no authorization_policies is public. There is no default that protects it.

app/views/pages/admin/orders.liquid

---
slug: admin/orders
authorization_policies:
  - is_orders_admin
---

Put it on the admin JSON endpoints too, not only on the pages people see.

Two Admin Areas

Say your site has an orders area and a content area. There are two ways to set this up.

  • The same people manage both. Use one role, admin, and one policy, is_admin. Put that policy on every page in both areas.
  • Different people manage each area. Use two roles, orders_admin and content_admin, and two policies, is_orders_admin and is_content_admin. Put each policy on the pages of its own area. A person who manages both gets both roles.

Do not protect admin pages with insites_only_allowed_if_logged_in. It lets in any signed-in user on the instance, not only your admins.

Step 6: Test It Signed Out

  1. Open each admin page while signed out. You should be sent to the sign-in page.
  2. Sign in as a user with no role. Each admin page should refuse you.
  3. Sign in as an orders admin. The orders pages should open and the content pages should refuse you.

Loading the page yourself is the only real proof. A policy listed in a file is an intention until you have seen it refuse a request.

What Happens to the Lockout

A shared password needed a lockout after wrong guesses. With admin users there is no shared password to guess. Put the limit on the sign-in page instead. See Rate Limiting by IP Address.

Have a suggestion for this page?

Didn't quite find what you are looking for or have feedback on how we can make the content better then we would love to hear from you. Please provide us feedback and we will get back to you shortly.