First impressions can be hard to shake. In Meteor’s case, that’s both good and bad.

When Meteor first came out, people were blown away by the ease with which you could build and deploy a real-time, reactive app. And this is still what attracts many users to the platform.

But on the other hand, to this day Meteor suffers from the perception that the framework isn’t secure. Just the other day, a friend told me they didn’t pick Meteor when evaluating technologies because of the framework’s “inherent security issues”.

So what’s going on? Can a framework used in production by thousands of people really be “inherently insecure”?

Read on to find out why this is not the case, where that misconception originally came from, and – most importantly – how to go about securing your Meteor apps.

The Early Days

Let’s jump in our time machine and go back to April 11, 2012. Meteor has just launched on Hacker News and caused a big splash, becoming one of the must upvoted threads of all time.

To quote the top comment:

My first impression of this: wow. If Meteor is all it appears to be, this is nothing short of revolutionary.

Of course, this being Hacker News, some people met the announcement with a healthy dose of skepticism:

This is not revolutionary. It is an attempt to wrap things in a friendlier package, while at the same time making something horribly insecure as a default install.

(That second commenter then goes on to give the Wright brother’s first manned flight as a better example of something “revolutionary”, which is admittedly a pretty high standard to live up to.)

So who’s right? The positive and optimistic comment, or the negative and cynical one?

Well, how about both?

You see, that first Meteor demo version didn’t yet support user accounts, so you had no way of restricting access to your data based on the connected user.

That all changed about six months later with the introduction of the Accounts system.

But for these first six months, anybody trying out Meteor would be playing with an alpha prototype that was absolutely not meant to be used anywhere near a production app.

So yes, that prototype version was insecure, but it was only because the Meteor team hadn’t gotten around to implementing this facet of the framework yet.

Insecure Defaults

You’d think two years would be enough time to shake off this stigma, but like I said, first impressions matter.

What’s more, there’s another reason why it’s easy to believe Meteor apps are insecure: by default, any new Meteor app you create with the meteor create myApp command will be insecure.

You might think I just defeated my own argument but wait, don’t close your browser tab just yet! Reality is actually a little more nuanced than that, as it tends to be.

You see, freshly-baked Meteor apps are insecure because of two packages, autopublish and insecure.

autopublish will take your server-side database and mirror all of its contents to the client (although it is smart enough to make an exception for password tokens and other really private stuff).

Autopublish in action

insecure on the other hand will grant any connected client the rights to modify any data in your database, just as if they had server access.

Why the hell would you ever want to do such a horribly dangerous thing? In a word: faster prototyping.

Well OK, that’s two words. Here’s a more detailed explanation straight from the Meteor docs:

By default, a new Meteor app includes the autopublish and insecure packages, which together mimic the effect of each client having full read/write access to the server’s database. These are useful prototyping tools, but typically not appropriate for production applications. When you’re ready, just remove the packages.

The autopublish and insecure packages greatly speed up the initial stages of app development. Need to access some data on the client? autopublish means that something like Posts.find() will just work out of the box.

And if you want to add data, it’s as easy as Posts.insert({title: 'my new post'}) thanks to insecure. And that will even work from your browser console, meaning you can get started without even building out a UI.

In other words, by postponing security considerations until a later stage, these two packages make the browser side just as easy to develop for as the server environment

Developer Responsibilities

I wouldn’t blame you if you think security should be taken into account right from the very first step. And in fact, now that I have more experience with Meteor, I tend to remove these two packages much earlier in the process.

But in practice, autopublish and insecure rarely pose a security risk: as soon as you remove them, you app will simply stop working until you reestablish the proper procedures.

Of course, bad things can happen if you don’t remove them. But I personally believe if you keep a package called “insecure” in a production app, you deserve part of the blame. After all, every environment comes with its own set of developer responsibilities, as XKCD famously taught us.

Exploits of a mom

Still, maybe Meteor could do more. For example, inject a small warning overlay in your app’s DOM when the insecure package is active. Or just show a warning message in your terminal output every time you run your app.

Displaying Data

Yet all this still doesn’t tell us how you make a Meteor app secure once you’ve removed autopublish and insecure.

To answer this, we’ll first of all consider the matter of displaying data. In other words, how do you make sure you’re not sharing private information with the world?

Meteor accomplishes this through a mechanism known as publications. I’ve written extensively about this topic before so I’m not going to rehash this too much, but briefly speaking: publications let you specify which subset of your database should be made publicly accessible.

Let’s suppose you have a Posts collection in your database. You could:

Publish all posts:

Meteor.publish('posts', function() {
  return Posts.find();
});

Only publish posts from a specific author:

Meteor.publish('posts', function(author) {
  return Posts.find({author: author});
});

Only publish the 30 most recent posts:

Meteor.publish('posts', function() {
  return Posts.find({}, {sort: {timestamp: -1}, limit: {30}});
});

Exclude the timestamp field from being published:

Meteor.publish('posts', function() {
  return Posts.find({}, {fields: {timestamp: 0}});
});

Only publish posts if a user is logged in:

Meteor.publish('posts', function() {
  if(this.userId){
    return Posts.find();
  }
});

You get the idea: Meteor gives you fine-grained control over each piece of data you publish.

Publications let you specify what’s private and what’s public

Writing Data With Methods

So we’ve seen how Meteor deals with displaying data. But what about inserting, updating, and deleting documents?

Meteor has two main ways of dealing with this issues: Methods and Allow/Deny.

Meteor Methods are like a remote control for your server: they let you call specific, predefined methods from the client and execute their code on the server.

For example, here’s how you’d define a method that logs any given string to the terminal output:

Meteor.methods({
  logString: function (myString) {
    console.log(myString);
  }
});

And here’s how you’d call it from anywhere in your client code (or even from within the browser console):

Meteor.call('logString', 'hello world!');

Using this principle, we can store any sensitive code in a method and be sure we’re executing it in the server’s controlled environment, rather than the untrusted world of the client:

Meteor.methods({
  insertNewPost: function (myTitle) {
    Posts.insert({title: myTitle});
  }
});

Which we call like this:

Meteor.call('insertNewPost', 'Lorem ipsum dolor sit amet.');

And since we can trust the server environment, we can easily add checks for various user properties:

Meteor.methods({
  insertNewPost: function (myTitle) {
    if(Meteor.user().isAdmin){
      Posts.insert({title: myTitle});
    }
  }
});

This works great, but we do lose the convenience of being able to trigger our database operations right in the client. This is where Allow/Deny comes in.

Writing Data With Allow/Deny

Allow and Deny are two server-side callbacks that execute for any insert, update, or remove database operations.

The operation is only permitted to go through if either:

  • At least one allow callback returns true.
  • No deny callback returns true.

This means that if you haven’t written any allow callbacks yet, no database operation will be permitted to go through from the client (assuming of course you’ve already removed the insecure package).

Here’s what a typical set of allow callbacks could look like (taken from Microscope’s code):

Posts.allow({
  update: ownsDocument,
  remove: ownsDocument
});

Each callback is calling the following ownsDocument function to check that the userId specified owns the document:

ownsDocument = function(userId, doc) {
  return doc && doc.userId === userId;
}

You’ll notice that we didn’t specify any insert callback. That’s because in Microscope inserting is taken care of by a Meteor Method, showing that both techniques can be combined according to your needs.

We talk about Allow and Deny in more details in the book, touching on topics like call order and practical use cases.

The Allow/Deny call order. “n/e” stands for “not executed”

Thinking About Security

When it comes to the web, it would be hard (and pretty stupid) to argue that security isn’t important.

The Meteor team are fully conscious of this, and that’s why they’ve always made security a priority. Every new release brings its own set of security improvements, from the browser-policy package, to improved OAuth security, to better password encryption.

So I’ll be the first to admit that there are many valid reasons for not using Meteor. But I honestly don’t believe that “inherent security issues” should be one of them.