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:
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:
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:
// 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 codeName | Type | Description | Required |
---|---|---|---|
roleName | String | Name of role. | Yes |
options | Object | Options:
| No |
Example:
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 codeName | Type | Description | Required |
---|---|---|---|
rolesNames | Array or String | Name(s) of role(s). | Yes |
parentName | String | Name of parent role. | Yes |
Example:
// 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 codeName | Type | Description | Required |
---|---|---|---|
rolesNames | Array or String | Name(s) of role(s). | Yes |
parentName | String | Name of parent role. | Yes |
Example:
// 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 codeName | Type | Description | Required |
---|---|---|---|
roleName | String | Name of role. | Yes |
Example:
// Delete role and all its assignments
await Roles.deleteRoleAsync("temp-role");
Roles.renameRoleAsync
Summary:
Rename an existing role.
Arguments:
Source codeName | Type | Description | Required |
---|---|---|---|
oldName | String | Old name of a role. | Yes |
newName | String | New name of a role. | Yes |
Example:
// 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 codeName | Type | Description | Required |
---|---|---|---|
users | Array or String | User ID(s) or object(s) with an | Yes |
roles | Array or String | Name(s) of roles to add users to. Roles have to exist. | Yes |
options | Object or String | Options:
| No |
Example:
// 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 codeName | Type | Description | Required |
---|---|---|---|
users | Array or String | User ID(s) or object(s) with an | Yes |
roles | Array or String | Name(s) of roles to add users to. Roles have to exist. | Yes |
options | Object or String | Options:
| No |
Example:
// 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 codeName | Type | Description | Required |
---|---|---|---|
users | Array or String | User ID(s) or object(s) with an | Yes |
roles | Array or String | Name(s) of roles to remove users from. Roles have to exist. | Yes |
options | Object or String | Options:
| No |
Example:
// 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 codeName | Type | Description | Required |
---|---|---|---|
oldName | String | Old name of a scope. | Yes |
newName | String | New name of a scope. | Yes |
Example:
// Rename a scope
await Roles.renameScopeAsync("department-1", "marketing");
Roles.removeScopeAsync
Summary:
Remove a scope and all its role assignments.
Arguments:
Source codeName | Type | Description | Required |
---|---|---|---|
name | String | The name of a scope. | Yes |
Example:
// Remove a scope and all its role assignments
await Roles.removeScopeAsync("old-department");
Roles.getAllRoles
Summary:
Retrieve cursor of all existing roles.
Arguments:
Source codeName | Type | Description | Required |
---|---|---|---|
queryOptions | Object | Options which are passed directly through to | No |
Example:
// 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 codeName | Type | Description | Required |
---|---|---|---|
roles | Array or String | Name of role or an array of roles. | Yes |
options | Object or String | Options:
| No |
Example:
// 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 codeName | Type | Description | Required |
---|---|---|---|
user | String or Object | User ID or an actual user object. | Yes |
roles | Array or String | Name of role or an array of roles to check against. If array,
will return | Yes |
options | Object or String | Options:
Alternatively, it can be a scope name string. | No |
Example:
// 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 codeName | Type | Description | Required |
---|---|---|---|
user | String or Object | User ID or an actual user object. | Yes |
options | Object or String | Options:
Alternatively, it can be a scope name string. | No |
Example:
// 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 codeName | Type | Description | Required |
---|---|---|---|
parentRoleName | String | The role you want to research. | Yes |
childRoleName | String | The role you expect to be among the children of parentRoleName. | Yes |
Example:
// 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 codeName | Type | Description | Required |
---|---|---|---|
user | String or Object | User ID or an actual user object. | Yes |
roles | Array or String | Name of roles to restrict scopes to. | No |
Example:
// 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:
// 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:
Migration to Core Version
If you are currently using the alanning:roles
package, follow these steps to migrate to the core version:
- Make sure you are on version 3.6 of
alanning:roles
first - Run any pending migrations from previous versions
- Switch all server-side role operations to use the async versions of the functions:
- createRoleAsync
- deleteRoleAsync
- addUsersToRolesAsync
- setUserRolesAsync
- removeUsersFromRolesAsync
- etc.
- Remove
alanning:roles
package:bashmeteor remove alanning:roles
- Add the core roles package:bash
meteor add roles
- 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
- Client-side role checks are for convenience only - always verify permissions on the server
- Publish only the role data that users need
- Use scopes to properly isolate role assignments
- Validate role names and scopes to prevent injection attacks
- Consider using more granular permissions over broad role assignments
Example Usage
Method Security
// 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
// 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
// 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);
},
});