Checking Meteor Methods - Best Practices & Guidelines - Security

A meteor method is a remote procedure call (RPC) and is - so to speak - an API definition of what functionality can be called on your server from some remote client.

It is thus very important that we secure our methods with all necessary checks and make sure that only legitimate requests are accepted.

We use meteor for our SaaS product and thus most of our meteor methods require at least a minimal set of sanity checks such as checking if a user is signed in.
This is easily achieved with something like this:

Meteor.methods({
    'some.method'() {
        const { userId } = this;
        if (!userId) throw new Meteor.Error(403, 'Permission Denied');

        // actual method body         
    }
});

Now, because our users need a paid subscription to be able to perform certain actions (or to perform certain actions a certain number of times) combined with the possibility to create projects and grant access to other users with specific permissions, our sanity checks have grown to something like this:

Meteor.methods({
    'some.method.you.pay.for'(someDocumentId) {
        check(someDocumentId, String);
        const { userId } = this;
        if (!userId) throw new Meteor.Error(403, 'Permission Denied');

        const document = Documents.findOne(someDocumentId);
        if (!document) throw new Meteor.Error(400, 'Document not found');

        if (document.owner !== userId) throw new Meteor.Error(403, 'Permission Denied');

        const hasSufficientCreditsFromSubscription = Meteor.call('getCreditsForUser');
        if (!hasSufficientCreditsFromSubscription) throw new Meteor.Error(400, 'Insufficient Credits');

        // etc. etc.
    }
});

Now imagine you have tens or hundreds of meteor methods that all need checks like these. We’re not yet there but it’s only a matter of time :grin:.

This is extremely hard to maintain. In the case that requirements change, new features are added or some checks are changed, maintaining and updating all methods becomes a nightmare. It also requires us to write a huge amount of boilerplate for something that could be solved more elegantly.

We have thought about adding some kind of hooks for meteor methods or try if we can apply decorators on meteor methods. Another approach would be to define a set of meteor methods that do the checks and call them instead.

The actual checks in our case can be much more complicated and usually include a subset of simple checks like if a user is signed in and if a resource exists themselves. That is, some checks are performed in order to perform more complicated checks. Thus, moving some of these checks to meteor methods requires us to perform some checks multiple times (because those methods are exposed again).

Do you know any guidelines or best practices from your experience that have helped you secure a large set of meteor methods for a defined but changing and growing set of checks?

You can decouple your codes using Services - the Single Responsibility Principle as described here http://www.meteor-tuts.com/chapters/3/services.html by the amazing @diaconutheodor

2 Likes

Also, consider using Alanning Roles package to manage permissions https://github.com/alanning/meteor-roles

Exactly what I would have suggested as well!

Check meteor’s validated methods:

For checking if the user has access to specific methods, we use mixins for general checks like if the user is logged in and a permission check function which you can also create as a mixin if the implementation is consistent enough

1 Like

I’m currently using this: https://www.npmjs.com/package/accesscontrol

1 Like

The guide for Methods (which explains the logic behind validated-method before introducing it) is excellent:

The good thing about ValidatedMethods is that they support mixins and can be extended.

Using the principles from meteor-tuts with ValidatedMethods, our method definitions for our SAAS product look like this:

export const createModule = new SecuredMethod({
    name: 'modules.create',
    allow: PermissionsMixin.LoggedIn,
    validate: moduleSchema.omit('_id').validator({ clean: true }),
    run(newModule) {
        PageSecurity.isEditableBy(newModule.pageId, this.userId);
        return ModuleService.create(newModule);
    },
});

So logged-in checks are handled by a mixin (didericis:permissions-mixin), which can also support roles, groups and custom validation functions.

Because ValidatedMethod is sub-classable, you can make various method classes with default permissions checks and validation.

In this example, we’ve sub-classed it as SecuredMethod that handles some of the basics and automatically includes the permissions mixin.
Each method is responsible for validation and permissions checks before delegating the real work to a service module.
This makes unit testing a breeze as each method and module has limited responsibilities and all side effects are isolated to different layers

2 Likes

I believe we learned this from you, then. I was trying to remember why we ended up with SecuredMethod() calls. Thanks. a lot :slight_smile: