In a previous post, I talked about using Basic Authentication to hide your Meteor application from prying eyes. Unfortunately, the most straight-forward way of implementing this kind of protection has its flaws.
To see those flaws, let’s imagine that we’ve set up a basic Meteor application with the kit:basic-auth
package and a Meteor method:
Meteor.methods({
foo: function() {
return "bar";
}
});
When we try to navigate to the application (http://localhost:3000/
), we notice that we can’t access the application without valid credentials. Great!
Bypassing Basic Auth
However, Jesse Rosenberger recently pointed out that kit:basic-auth
, and similar packages such as jabbslad:basic-auth
, do not provide Basic Auth protection for WebSocket connections. This means that any external user can easily bypass this authentication mechanism and access your Meteor methods and publications.
For example, an external user could connect directly to your application using Meteor’s DDP API and call your "foo"
method:
var connection = DDP.connect("http://localhost:3000/");
connection.call("foo", function(err, res) {
console.log(res);
});
Any unauthorized user that runs the above code will receive a result of "bar"
from the "foo"
method.
This is a bad thing.
Calling In the Dark
While the DDP API gives users access to all of your Meteor methods and publications, it doesn’t reveal those methods and publications. In order to call a method or subscribe to a publication, a user needs to know its name.
However, this kind of security through obscurity shouldn’t be considered any real protection. An attacker eager to discover your DDP endpoints could build a brute forcer that guesses method and publication names in an attempt to uncover your endpoints.
A Better Basic Auth
At first glance, the kit:basic-auth
and jabbslad:basic-auth
packages seem to be doing all the right things. They’re injecting the Basic Auth check as a piece of connect middleware at the head of the stack which, in theory, should catch all HTTP traffic and verify the user’s credentials.
Unfortunately, the Meteor framework establishes the socket connection long before any of these middleware methods are called. This means that Basic Auth is ignored during the WebSocket handshake and upgrade process.
One possible technique for overcoming this middleware issue is to “overshadow” all "request"
and "upgrade"
listeners and inject our Basic Auth check there. The Meteor framework does this exact thing to support raw WebSocket connections.
However, a more straightforward approach to this problem may be to move your application behind a proxy such as HAProxy, or NGINX and implement Basic Auth at that level. The proxy would protect all assets and endpoints, including the /sockjs/.../websocket
endpoint, which is used to establish a WebSocket connection with the server.
Final Thoughts & Thanks
I’d like to give a massive thanks to Jesse Rosenberger who pointed out this issue to me, and gave me a huge amount of very helpful information and observations.
I’d also like to apologize to anyone hiding applications behind a package-based Basic Auth guard based on my advice. I’ve updated my previous post on this subject to reflect what I’ve learned and pointed out the current shortcomings of this package-based approach.