Skip to content


Switch branches/tags

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time

Meteor package that adds a restricted-access state and autologin links to alanning:roles.

The main use case is sending an email or sms to your user with a link to your app that contains an OTP (one-time password) that automatically logs them in (so they don't have to enter their username/password or do OAuth):

Josh Owens just commented on your blog post:

If you want the user to be fully logged in, use the package loren:login-links. If you want the user to be temporarily logged in with restricted permissions, use this package. The login is temporary - it only lasts for the duration of the DDP connection (it uses login-links connectionLogin). You can use both packages together, as long as you use different type names and call LoginLinks.setTypes before Roles.setRestrictionTypes.


Basic usage

  1. Configure your restriction types.
  2. Generate an access token.
  3. Put it in a secure HTTPS URL and send it to the user via email, sms, etc.
  4. When the user clicks the link, get the token from the URL and use it to log in the user.

On server

alice.roles // ['user', 'admin']

  userOnly: {roles: ['user']}

token = Roles.generateRestrictedAccessToken(alice, {type: 'userOnly'});

  text: 'Click this:' + token,

You could also use the token for all your emails to users, adding it as a query parameter that works on any route:

text: 'Josh Owens just commented on your post:' + token

Then on client

if (! Meteor.userId()) {
  token = get token from URL (depends on your router and link format)

  Roles.restrictedLogin(token, function(e, r) {
    if (!e) {
      alice = Meteor.user();
      Roles.userIsInRole(alice, ['user']); // true
      Roles.userIsInRole(alice, ['admin']); // false



You can configure restrictions using types or on a per-token basis.

The roles a user has in a restricted state is the intersection of the restricted roles list you configure and their normal roles (user.roles). For example in the below scenario, the restricted user would only have the user role.

// alice.roles is ['user', 'admin']

Roles.generateRestrictedAccessToken(alice, {roles: ['user', 'editor']});


When a login is attempted with a token that is expired, a 'login-links/token-expired' error will be thrown. The default token expiration is one day.

You can configure expiration in three ways:

  • globally: Roles.setDefaultExpirationInSeconds(60 * 60); // one hour (call from both client and server)
  • per type
  • per token

Changes to Roles package

The restricted roles are used for Roles.userIsInRole(user, roles, [group]) when user is the logged-in user, as well as Roles.getRolesForUser(user, [group]) and the isInRole UI helper. They are not used for getUsersInRole or getGroupsForUser - those and all other functions remain unchanged from the base Roles package.

Restriction information is stored on the DDP connection. When you check roles inside of methods, it's easy for roles-restricted to access the connection. However, when you check roles outside of a method context, you must pass the context as a final parameter.

  • userIsInRole(user, roles, [group], [context])
  • getRolesForUser(user, [group], [context]

For instance inside of a publish function:

Meteor.publish('data', function() {
  Roles.userIsInRole(user, ['editor'], null, this)
  Roles.userIsInRole(user, null, this)

If you don't have a publish context, then you can use {unrestricted: true} to do a check that assumes the user is unrestricted, or you can create an object to pass in:

  userId: String
  connection: {
    _roles: {
      unrestricted: true
    _roles: {
      restrictedRoles: {
        roles: ['user']
        group: 'group1'

The roles will be restricted if user matches context.userId and context.connection._roles.unrestricted is not true.

Security note

See login-links security note.



  typeName1: {
    roles: ['role1', 'role2', ...]
    group: 'group1' // if you use groups
    expirationInSeconds: 10 * 60 // optional
  typeName2: ...

Roles.generateRestrictedAccessToken(alice, {type: 'typeName1'});

Using types is optional. If used, call from both server and client.


Roles.generateRestrictedAccessToken(user, opts) (server)

  • user: userId or user object
  • opts:
    • type: String
    • roles: [String]
    • group: String (if you use groups)
    • expiresInSeconds: Integer (optional)

opts must include either type or roles. The token is 43 alphanumeric characters, and only the hashed version is stored in the DB.


Roles.restrictedLogin(token, cb) (client)

See login-links connectionLogin


{{isInRoleWhenUnrestricted role group}} (client)

Template helper, analagous to isInRole, but ignores any current restriction.



Roles.onResumeAttemptCompleted(cb) (client)

The cb function is provided a boolean argument loggedIn (whether the resume attempt was successful).

In the basic example, we check Meteor.userId() at load time before doing a restrictedLogin - if the user is already logged in, we don't need to do a restricted login.

if (! Meteor.userId())
  Roles.restrictedLogin(token, cb);

Meteor.userId() is optimistically set by Meteor at pageload when Meteor is in the process of doing a resume login. In some cases - if the resume token has expired or been removed from the database (for instance by Meteor.logoutOtherClients), then the resume login will fail, and Meteor.userId() will be set to null. To handle these cases, you can do the following:

token = // get from URL

if (Meteor.userId()) {
  Roles.onResumeAttemptCompleted(function(loggedIn) {
    if (! loggedIn) 
      Roles.restrictedLogin(token, cb);
} else {
  Roles.restrictedLogin(token, cb);


var hook = Roles.onResumeAttemptCompleted(fn)



See whether the current connection is in a restricted state or not.

From inside a publish function, instead use Roles.isUnrestrictedFromPublish(this).



opts must include either type or roles.

Manually place the connection in a restricted state. When called on server, it only applies to the server side of the connection. When called on the client, it is applied to both sides.

Note that this only lasts for the duration of the connection, so if they have a valid resume token (localStorage.getItem('Meteor.loginToken')), the user will be unrestricted on reconnect, on reload, or in other browser tabs.

Package dev

ES6 without semicolons


git clone
cd roles-restricted
meteor test-packages ./
open localhost:3000


Thanks to Share911 for sponsoring 👏 – the best emergency response system for your organization.



Adds restricted-access state and autologin links to alanning:roles







No packages published