Playing around with Meteor, I have found that even with the insecure package removed, the client can change the Meteor.userId function. For example,
Meteor.userId=function() {return "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"}
as can be done with Meteor.default_connection.userId()
(the redirected function). How do I secure this?
This is a great question because it shows how the Meteor security model works.
There's no security issue here because Meteor never trusts the client code.
In Meteor, only the server decides what data each client has access to (see Meteor.publish) and what data each client is allowed to change (see Meteor.allow). When a client authenticates to the server, the server stores the user's ID. Until that client logs out, it provides that ID to your Meteor.publish
and Meteor.allow
functions on the server as userId
.
Meteor also sends the user ID down on the client, because of course you want to change how the client behaves and what's on the screen based on who is logged in. And as you say, we can't stop a rogue client from arbitrarily changing any of its JavaScript code to change what it thinks the user ID is! But doing that doesn't give the client any new permissions, because it's still only the server code that makes the security decisions.
You can try this out using the secure parties application:
- Make a parties app with
$ meteor create --example parties
- Create a user account and double click on the map to create a party. Check the box to make it a private party.
- Open the JavaScript console and type
Meteor.userId()
to get your user`s ID.
- Log out. The party will disappear from the screen because the server won't publish it to any other user.
- Now, go into the console and overwrite
Meteor.userId()
with a new function that returns the ID you want.
So now you've faked the client to think that it's your user. But the server knows better. There still won't be a party on the screen, and you can't update the Parties collection to change that party information.
In fact, it's completely safe to set the client user ID to anything you want! You can reach right into the accounts system and call Meteor.default_connection.setUserId("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee");
. Try it, and you'll see that the login button in the upper right corner turns into an animation. That's because the client is calling Meteor.user()
to show the email address of the logged in user you just set. But because you haven't logged into the server as that user, it's not publishing any information about that user and you just get the spinny.
This is a very strong security model. You don't have to worry about any of the client code, even though in most apps that's where most of the code lives! As long as you write secure server methods, publish functions, and allow/deny rules, you're completely locked down no matter what the client tries to do.
I just tested Meteor using two browsers and copied the local storage Meteor.userId
and Meteor.loginToken
between each browser and they both logged me in as the same person. When I logged out of one I was still able to publish in the other.
I do not think this is as secure as is being made out.
If I can copy these values and still be seen as same user, even when I am using a different browser, then it is not secure at all.
Update
Upon reflection...
I suppose it would be possible to log the IP address of the user when they login. Then if a user tries to access and the IP address is not the same you could ask them to log in again.