Skip to content

Roles

Authorization package for Meteor - compatible with built-in accounts package.

Available since Meteor 3.1.0 (previously alanning:roles)

Installation

To add roles to your application, run this command in your terminal:

bash
meteor add roles

Overview

The roles package lets you attach roles to users and then check against those roles when deciding whether to grant access to Meteor methods or publish data. The core concept is simple - you create role assignments for users and then verify those roles later. This package provides helper methods to make the process of adding, removing, and verifying roles easier.

Concepts

Roles vs Permissions

Although named "roles", you can define your roles, scopes or permissions however you like. They are essentially tags assigned to users that you can check later.

You can have traditional roles like admin or webmaster, or more granular permissions like view-secrets, users.view, or users.manage. Often, more granular permissions are better as they handle edge cases without creating many higher-level roles.

Role Hierarchy

Roles can be organized in a hierarchy:

  • Roles can have multiple parents and children (subroles)
  • If a parent role is assigned to a user, all its descendant roles also apply
  • This allows creating "super roles" that aggregate permissions

Example hierarchy setup:

js
import { Roles } from "meteor/roles";

// Create base roles
await Roles.createRoleAsync("user");
await Roles.createRoleAsync("admin");

// Create permission roles
await Roles.createRoleAsync("USERS_VIEW");
await Roles.createRoleAsync("POST_EDIT");

// Set up hierarchy
await Roles.addRolesToParentAsync("USERS_VIEW", "admin");
await Roles.addRolesToParentAsync("POST_EDIT", "admin");
await Roles.addRolesToParentAsync("POST_EDIT", "user");

Scopes

Scopes allow users to have independent sets of roles. Use cases include:

  • Different communities within your app
  • Multiple tenants in a multi-tenant application
  • Different resource groups

Users can have both scoped roles and global roles:

  • Global roles apply across all scopes
  • Scoped roles only apply within their specific scope
  • Scopes are independent of each other

Example using scopes:

js
// Assign scoped roles
await Roles.addUsersToRolesAsync(userId, ["manage-team"], "team-a");
await Roles.addUsersToRolesAsync(userId, ["player"], "team-b");

// Check scoped roles
await Roles.userIsInRoleAsync(userId, "manage-team", "team-a"); // true
await Roles.userIsInRoleAsync(userId, "manage-team", "team-b"); // false

// Assign global role
await Roles.addUsersToRolesAsync(userId, "super-admin", null);

// Global roles work in all scopes
await Roles.userIsInRoleAsync(userId, ["manage-team", "super-admin"], "team-b"); // true

Role Management

Roles.createRoleAsync

Summary:

Create a new role.

Arguments:

Source code
NameTypeDescriptionRequired
roleNameString

Name of role.

Yes
optionsObject

Options:

  • unlessExists: if true, exception will not be thrown in the role already exists
No

Example:

js
import { Roles } from "meteor/roles";

// Create a new role
await Roles.createRoleAsync("admin");

// Create if doesn't exist
await Roles.createRoleAsync("editor", { unlessExists: true });

Modifying Roles

Roles.addRolesToParentAsync

Summary:

Add role parent to roles.

Arguments:

Source code
NameTypeDescriptionRequired
rolesNamesArray or String

Name(s) of role(s).

Yes
parentNameString

Name of parent role.

Yes

Example:

js
// Make 'editor' a child role of 'admin'
await Roles.addRolesToParentAsync("editor", "admin");

// Add multiple child roles
await Roles.addRolesToParentAsync(["editor", "moderator"], "admin");

Roles.removeRolesFromParentAsync

Summary:

Remove role parent from roles. Other parents are kept (role can have multiple parents). For users which have the parent role set, removed subrole is removed automatically.

Arguments:

Source code
NameTypeDescriptionRequired
rolesNamesArray or String

Name(s) of role(s).

Yes
parentNameString

Name of parent role.

Yes

Example:

js
// Remove 'editor' as child role of 'admin'
await Roles.removeRolesFromParentAsync("editor", "admin");

Roles.deleteRoleAsync

Summary:

Delete an existing role. If the role is set for any user, it is automatically unset.

Arguments:

Source code
NameTypeDescriptionRequired
roleNameString

Name of role.

Yes

Example:

js
// Delete role and all its assignments
await Roles.deleteRoleAsync("temp-role");

Roles.renameRoleAsync

Summary:

Rename an existing role.

Arguments:

Source code
NameTypeDescriptionRequired
oldNameString

Old name of a role.

Yes
newNameString

New name of a role.

Yes

Example:

js
// Rename an existing role
await Roles.renameRoleAsync("editor", "content-editor");

Assigning Roles

Roles.addUsersToRolesAsync

Summary:

Add users to roles. Adds roles to existing roles for each user.

Arguments:

Source code
NameTypeDescriptionRequired
usersArray or String

User ID(s) or object(s) with an _id field.

Yes
rolesArray or String

Name(s) of roles to add users to. Roles have to exist.

Yes
optionsObject or String

Options:

  • scope: name of the scope, or null for the global role
  • ifExists: if true, do not throw an exception if the role does not exist
No

Example:

js
// Add global roles
await Roles.addUsersToRolesAsync(userId, ["admin", "editor"]);

// Add scoped roles
await Roles.addUsersToRolesAsync(userId, ["manager"], "department-a");

// Add roles to multiple users
await Roles.addUsersToRolesAsync([user1Id, user2Id], ["user"]);

Roles.setUserRolesAsync

Summary:

Set users' roles. Replaces all existing roles with a new set of roles.

Arguments:

Source code
NameTypeDescriptionRequired
usersArray or String

User ID(s) or object(s) with an _id field.

Yes
rolesArray or String

Name(s) of roles to add users to. Roles have to exist.

Yes
optionsObject or String

Options:

  • scope: name of the scope, or null for the global role
  • anyScope: if true, remove all roles the user has, of any scope, if false, only the one in the same scope
  • ifExists: if true, do not throw an exception if the role does not exist
No

Example:

js
// Replace user's global roles
await Roles.setUserRolesAsync(userId, ["editor"]);

// Replace scoped roles
await Roles.setUserRolesAsync(userId, ["viewer"], "project-x");

// Clear all roles in scope
await Roles.setUserRolesAsync(userId, [], "project-x");

Roles.removeUsersFromRolesAsync

Summary:

Remove users from assigned roles.

Arguments:

Source code
NameTypeDescriptionRequired
usersArray or String

User ID(s) or object(s) with an _id field.

Yes
rolesArray or String

Name(s) of roles to remove users from. Roles have to exist.

Yes
optionsObject or String

Options:

  • scope: name of the scope, or null for the global role
  • anyScope: if set, role can be in any scope (scope option is ignored)
No

Example:

js
// Remove global roles
await Roles.removeUsersFromRolesAsync(userId, ["admin"]);

// Remove scoped roles
await Roles.removeUsersFromRolesAsync(userId, ["manager"], "department-a");

// Remove roles from multiple users
await Roles.removeUsersFromRolesAsync([user1Id, user2Id], ["temp-role"]);

Roles.renameScopeAsync

Summary:

Rename a scope.

Arguments:

Source code
NameTypeDescriptionRequired
oldNameString

Old name of a scope.

Yes
newNameString

New name of a scope.

Yes

Example:

js
// Rename a scope
await Roles.renameScopeAsync("department-1", "marketing");

Roles.removeScopeAsync

Summary:

Remove a scope and all its role assignments.

Arguments:

Source code
NameTypeDescriptionRequired
nameString

The name of a scope.

Yes

Example:

js
// Remove a scope and all its role assignments
await Roles.removeScopeAsync("old-department");

Roles.getAllRoles

Summary:

Retrieve cursor of all existing roles.

Arguments:

Source code
NameTypeDescriptionRequired
queryOptionsObject

Options which are passed directly through to Meteor.roles.find(query, options).

No

Example:

js
// Get all roles sorted by name
const roles = Roles.getAllRoles({ sort: { _id: 1 } });

// Get roles with custom query
const customRoles = Roles.getAllRoles({
  fields: { _id: 1, children: 1 },
  sort: { _id: -1 },
});

Roles.getUsersInRoleAsync

Summary:

Retrieve all users who are in target role.

Arguments:

Source code
NameTypeDescriptionRequired
rolesArray or String

Name of role or an array of roles.

Yes
optionsObject or String

Options:

  • scope: name of the scope to restrict roles to
  • anyScope: if set, role can be in any scope
  • onlyScoped: if set, only roles in the specified scope are returned
  • queryOptions: options which are passed directly through to Meteor.users.find(query, options)
No

Example:

js
// Find all admin users
const adminUsers = await Roles.getUsersInRoleAsync("admin");

// Find users with specific roles in a scope
const scopedUsers = await Roles.getUsersInRoleAsync(
  ["editor", "writer"],
  "blog"
);

// Find users with custom options
const users = await Roles.getUsersInRoleAsync("manager", {
  scope: "department-a",
  queryOptions: {
    sort: { createdAt: -1 },
    limit: 10,
  },
});

Checking Roles

Roles.userIsInRoleAsync

Summary:

Check if user has specified roles.

Arguments:

Source code
NameTypeDescriptionRequired
userString or Object

User ID or an actual user object.

Yes
rolesArray or String

Name of role or an array of roles to check against. If array, will return true if user is in any role. Roles do not have to exist.

Yes
optionsObject or String

Options:

  • scope: name of the scope; if supplied, limits check to just that scope the user's global roles will always be checked whether scope is specified or not
  • anyScope: if set, role can be in any scope (scope option is ignored)

Alternatively, it can be a scope name string.

No

Example:

js
// Check global role
const isAdmin = await Roles.userIsInRoleAsync(userId, "admin");

// Check any of multiple roles
const canEdit = await Roles.userIsInRoleAsync(userId, ["editor", "admin"]);

// Check scoped role
const isManager = await Roles.userIsInRoleAsync(
  userId,
  "manager",
  "department-a"
);

// Check role in any scope
const hasRole = await Roles.userIsInRoleAsync(userId, "viewer", {
  anyScope: true,
});

Roles.getRolesForUserAsync

Summary:

Retrieve user's roles.

Arguments:

Source code
NameTypeDescriptionRequired
userString or Object

User ID or an actual user object.

Yes
optionsObject or String

Options:

  • scope: name of scope to provide roles for; if not specified, global roles are returned
  • anyScope: if set, role can be in any scope (scope and onlyAssigned options are ignored)
  • onlyScoped: if set, only roles in the specified scope are returned
  • onlyAssigned: return only assigned roles and not automatically inferred (like subroles)
  • fullObjects: return full roles objects (true) or just names (false) (onlyAssigned option is ignored) (default false) If you have a use-case for this option, please file a feature-request. You shouldn't need to use it as it's result strongly dependent on the internal data structure of this plugin.

Alternatively, it can be a scope name string.

No

Example:

js
// Get user's global roles
const globalRoles = await Roles.getRolesForUserAsync(userId);

// Get scoped roles
const deptRoles = await Roles.getRolesForUserAsync(userId, "department-a");

// Get all roles including inherited
const allRoles = await Roles.getRolesForUserAsync(userId, {
  anyScope: true,
  fullObjects: true,
});

Roles.isParentOfAsync

Summary:

Find out if a role is an ancestor of another role.

Arguments:

Source code
NameTypeDescriptionRequired
parentRoleNameString

The role you want to research.

Yes
childRoleNameString

The role you expect to be among the children of parentRoleName.

Yes

Example:

js
// Check if admin is a parent of editor
const isParent = await Roles.isParentOfAsync("admin", "editor");

// Can be used to check inheritance chains
const hasPermission = await Roles.isParentOfAsync("super-admin", "post-edit");

Roles.getScopesForUserAsync

Summary:

Retrieve users scopes, if any.

Arguments:

Source code
NameTypeDescriptionRequired
userString or Object

User ID or an actual user object.

Yes
rolesArray or String

Name of roles to restrict scopes to.

No

Example:

js
// Get all scopes for user
const allScopes = await Roles.getScopesForUserAsync(userId);

// Get scopes where user has specific roles
const editorScopes = await Roles.getScopesForUserAsync(userId, ["editor"]);

Publishing Roles

Role assignments need to be published to be available on the client. Example publication:

js
// Publish user's own roles
Meteor.publish(null, function () {
  if (this.userId) {
    return Meteor.roleAssignment.find({ "user._id": this.userId });
  }
  this.ready();
});

// Publish roles for specific scope
Meteor.publish("scopeRoles", function (scope) {
  if (this.userId) {
    return Meteor.roleAssignment.find({ scope: scope });
  }
  this.ready();
});

Client only APIs

On the client alongside the async methods, you can use the sync versions of the functions:

  • Roles.userIsInRole(userId, roles, scope)
  • Roles.getRolesForUser(userId, scope)
  • Roles.getScopesForUser(userId)
  • Roles.isParentOf(parent, child)
  • Roles.getUsersInRole(role, scope)
  • Roles.getAllRoles(options)
  • Roles.createRole(role, options)
  • Roles.addUsersToRoles(userId, roles, scope)
  • Roles.setUserRoles(userId, roles, scope)
  • Roles.removeUsersFromRoles(userId, roles, scope)
  • Roles.addRolesToParent(child, parent)
  • Roles.removeRolesFromParent(child, parent)
  • Roles.deleteRole(role)
  • Roles.renameRole(oldRole, newRole)
  • Roles.renameScope(oldScope, newScope)
  • Roles.removeScope(scope)

Using with Templates

The roles package automatically provides an isInRole helper for templates:

handlebars
{{#if isInRole "admin"}}
  <div class="admin-panel">
    <!-- Admin only content -->
  </div>
{{/if}}

{{#if isInRole "editor,writer" "blog"}}
  <div class="editor-tools">
    <!-- Blog editor tools -->
  </div>
{{/if}}

Migration to Core Version

If you are currently using the alanning:roles package, follow these steps to migrate to the core version:

  1. Make sure you are on version 3.6 of alanning:roles first
  2. Run any pending migrations from previous versions
  3. Switch all server-side role operations to use the async versions of the functions:
    • createRoleAsync
    • deleteRoleAsync
    • addUsersToRolesAsync
    • setUserRolesAsync
    • removeUsersFromRolesAsync
    • etc.
  4. Remove alanning:roles package:
    bash
    meteor remove alanning:roles
  5. Add the core roles package:
    bash
    meteor add roles
  6. Update imports to use the new package:
    js
    import { Roles } from "meteor/roles";

The sync versions of these functions are still available on the client.

Security Considerations

  1. Client-side role checks are for convenience only - always verify permissions on the server
  2. Publish only the role data that users need
  3. Use scopes to properly isolate role assignments
  4. Validate role names and scopes to prevent injection attacks
  5. Consider using more granular permissions over broad role assignments

Example Usage

Method Security

js
// server/methods.js
Meteor.methods({
  deletePost: async function (postId) {
    check(postId, String);

    const canDelete = await Roles.userIsInRoleAsync(
      this.userId,
      ["admin", "moderator"],
      "posts"
    );

    if (!canDelete) {
      throw new Meteor.Error("unauthorized", "Not authorized to delete posts");
    }

    Posts.remove(postId);
  },
});

Publication Security

js
// server/publications.js
Meteor.publish("secretDocuments", async function (scope) {
  check(scope, String);

  const canView = await Roles.userIsInRoleAsync(
    this.userId,
    ["view-secrets", "admin"],
    scope
  );

  if (canView) {
    return SecretDocs.find({ scope: scope });
  }

  this.ready();
});

User Management

js
// server/users.js
Meteor.methods({
  promoteToEditor: async function (userId, scope) {
    check(userId, String);
    check(scope, String);

    const canPromote = await Roles.userIsInRoleAsync(
      this.userId,
      "admin",
      scope
    );

    if (!canPromote) {
      throw new Meteor.Error("unauthorized");
    }

    await Roles.addUsersToRolesAsync(userId, ["editor"], scope);
  },
});