├── server ├── startup.js └── methods │ └── update-user-name.js └── README.md /server/startup.js: -------------------------------------------------------------------------------- 1 | Meteor.startup(function(){ 2 | 3 | /* 4 | UPDATED: 01/16/15 5 | Thanks to @richsilvo and Ben Green (on Crater.io) for explaining that the 6 | auth token is unnecessary. Instead, we can make use of this.connection 7 | within our method to determine whether a method was called from the server. 8 | Here, we've defined a setTimeout that will fire a call to our updateUserName 9 | method 5 seconds after startup, successfully calling our method. If we 10 | attempted to call updateUserName from the client (e.g. your browser console) 11 | Meteor would throw the error we've defined in our method. Nice! 12 | */ 13 | setTimeout(function(){ 14 | var update = { 15 | id: Meteor.userId(), 16 | name: "John Doe" 17 | } 18 | 19 | Meteor.call('updateUserName', update, function(){ 20 | if (error) { 21 | console.log(error); 22 | } 23 | }); 24 | }, 5000); 25 | 26 | }); 27 | -------------------------------------------------------------------------------- /server/methods/update-user-name.js: -------------------------------------------------------------------------------- 1 | Meteor.methods({ 2 | 3 | // Note: this requires the Check package to be installed. You should also install 4 | // the audit-argument-checks package to help catch yourself forgetting to check 5 | // arguments passed to methods. 6 | updateUserName: function(update){ 7 | // First, check our update object against our expected pattern. 8 | check(update, {id: String, name: String}); 9 | 10 | /* 11 | UPDATED: 01/16/15 12 | Thanks to @richsilvo and Ben Green (on Crater.io) for explaining that the 13 | auth token is unnecessary. Instead, we can test whether 14 | this.connection == null in our method (this would be TRUE if the method is 15 | called from your server code). See the setTimeout() pattern in 16 | /server/startup.js to see that when called from the server (i.e. you don't 17 | do Meteor.call() in your browser console, it returns null). If called from 18 | the client, this.connection returns an object with connection data. 19 | */ 20 | if ( this.connection == null ) { 21 | Meteor.users.update(update.id,{ 22 | $set: { 23 | "profile.name": update.name 24 | } 25 | }); 26 | } else { 27 | throw new Meteor.Error('server-only-method', 'Sorry, this method can only be called from the server.'); 28 | } 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #### Preventing Client Calls to a Server-Only Method 2 | 3 | ### DOES NOT WORK WITH METEOR 1.5.1+ 4 | 5 | Fair warning, due to change in the behavior of the `this.connection` technique described below, this will _not_ work with Meteor 1.5.1+. If you're using Meteor 1.5.1+, it's best to avoid calling Meteor Methods from the server directly, and instead, [defining common modules that can be called from within your Methods](https://themeteorchef.com/tutorials/using-the-module-pattern). 6 | 7 | --- 8 | 9 | ### Heads Up! 10 | **This snippet was refactored to be a little bit simpler on 01/16/15. Big thanks to 11 | [@richsilvo](http://twitter.com/richsilvo) and [Ben Green](http://crater.io/users/numtel) for suggesting the simplification :)** 12 | 13 | ---- 14 | 15 | Technically, Meteor's methods are exposed to the client, meaning any user can determine a method we've written and call it from the client. Sometimes, though, we have server methods that we *do not* want accessible from the client. The following pattern showcases how to prevent client-side method calls by ~~making use of the Random package and a bit of elbow grease~~ checking whether `this.connection == null` inside of our method. 16 | 17 | **Note:** Server-only Meteor methods are not necessary since normal server side only functions could be used instead. Using server-only Meteor methods can help provide a few advantages when it comes to code organization, readability and understanding. Method declarations (server-only and non-server-only) can be organized and logically grouped together. `Method.call` references visually stand out in code and imply a certain behavior, making it easier to understand what's happening where. 18 | 19 | #### What we're trying to accomplish 20 | 21 | As an example, we have a method called `updateUserName` that we only want accessible on the server. We do *not* want a user (or other bad actor) able to update their name from the client. So, by default if our user called `Meteor.call('updateUserName')` on the client... 22 | 23 | ``` 24 | var update = { 25 | id: Meteor.userId(), 26 | name: "John Doe" 27 | } 28 | 29 | Meteor.call('updateUserName', update, function(error){ 30 | if(error){ 31 | console.log(error); 32 | } 33 | }); 34 | ``` 35 | 36 | And our method didn't check to see _where_ the method was called from... 37 | 38 | ``` 39 | Meteor.methods({ 40 | updateUserName: function(update){ 41 | check(update, {id: String, name: String}); 42 | 43 | Meteor.users.update(update.id,{ 44 | $set: { 45 | "profile.name": update.name 46 | } 47 | }); 48 | } 49 | }); 50 | ``` 51 | 52 | ...our method would perform the update. Instead, we want to make sure that our method never allows this to happen. 53 | 54 | #### Our Solution 55 | Because `this.connection == null` on the server, we can wrap whatever function our method is trying to perform in an `if` statement testing for this case: 56 | 57 | ``` 58 | Meteor.methods({ 59 | updateUserName: function(update){ 60 | check(update, {id: String, name: String}); 61 | if ( this.connection == null ) { 62 | Meteor.users.update(update.id,{ 63 | $set: { 64 | "profile.name": update.name 65 | } 66 | }); 67 | } else { 68 | throw new Meteor.Error('server-only-method', 'Sorry, this method can only be called from the server.'); 69 | } 70 | } 71 | }); 72 | ``` 73 | 74 | Easy peasy. Now, if we were to call our method from the client (e.g. in our browser's console), Meteor would throw an error because `this.connection` would return a connection object and _not_ null like we want. To test this out, we can setup a `setTimeout()` callback in our `Meteor.startup()` callback to test firing our method directly from the server: 75 | 76 | ``` 77 | Meteor.startup(function(){ 78 | setTimeout(function(){ 79 | var update = { 80 | id: Meteor.userId(), 81 | name: "John Doe" 82 | } 83 | 84 | Meteor.call('updateUserName', update, function(){ 85 | if (error) { 86 | console.log(error); 87 | } 88 | }); 89 | }, 5000); 90 | }); 91 | ``` 92 | 93 | Now, after our server starts up and a five second wait, our method will be called successfully and our user will be updated! 94 | 95 | #### Wrap Up 96 | So there we go, a quick pattern for securing methods so that they can only be called from the server! Handy. This was discovered in passing while I was working on Recipe #5, Building a SaaS with Meteor: Stripe where I'll be showcasing how to integrate Stripe in a Meteor app. If you want to see it in action, make sure to [sign up for the mailing list](http://themeteorchef.us8.list-manage2.com/subscribe?u=8cffd428bf025d80425da063c&id=a347eecb12). If you have any improvements to this snippet, feel free to share and submit a Pull Request! 97 | 98 | Again, big thanks to [@richsilvo](http://twitter.com/richsilvo) and [Ben Green](http://crater.io/users/numtel) for the tip on `this.connection == null`! 99 | --------------------------------------------------------------------------------