maccman / juggernaut

[DEPRECATED] Realtime server push with node.js, WebSockets and Comet
http://alexmaccaw.co.uk
MIT License
1.63k stars 188 forks source link

Really private channels ? #14

Open Sephi-Chan opened 14 years ago

Sephi-Chan commented 14 years ago

Hello, I'm trying Juggernaut in order to develop a webgame (with Rails) and I need private channels.

I looked at your example about that but it's seems to not be secure : everybody can open a Javascript console on the website and subscribe to a channel, no ?

So, there is a way to have really private channels ? Using a callback (called on subscribe) to know (by asking to the Rails server, in my case) if the Juggernaut server should accept/refuse the connection ? Does this feature exists ? Would it be hard to implement ?

I have another idea : using encryption with private and public keys (both of these keys have to be injected to the client-side Javascript code to be able to decrypt datas received and crypt data for sending). Can it be reliable ?

PlasticLizard commented 14 years ago

This appears to be exactly how PusherApp works, so I bet it's possible and reliable and I agree, this would be a very nice feature, secure, private messaging is a must for a wide variety of apps.

mrbrdo commented 14 years ago

I don't know about encryption in Javascript... But what you are suggesting indeed is something in need. I solved it in my project by using a random generated string in my Rails app. Then I output this string in a meta tag in the HTML so javascript generates the channel name (like /channelname/secret_string). It is not the best solution but is pretty ok security wise if the string length is appropriate. May affect network performance a bit though (more data to send with each message).

Sephi-Chan commented 14 years ago

This solution is more obfuscation than security but i think i'll use it for my needs : it's really simple and I don't need to be really confident, it's just a webgame. :)

mrbrdo commented 14 years ago

Yes I know it's not the proper way, just dirty fix. I hope in the future it will be possible in Juggernaut. Do you have any ideas how you would implement it in Juggernaut? Without encryption... The problem is usually you will have to connect the http web request (to rails app) with socket connection (session id). What do you think the best way would be to do that? IP is not good because a user can open more than 1 browser or have LAN computers... I think some kind of ID or token generated by Rails (like I wrote above anyway) should be a pretty ok solution. Then you can authenticate the user based on this ID. Of course this kind of way is still susceptible to sniffing attack, but I don't think that's important here.

PlasticLizard commented 14 years ago

What pusher does is pretty much what you suggest - the JS library makes an AJAX call to your server via some well known URL. Your server uses session info to authenticate you like it would authenticate you for any other request, except that it also has the channel name and the socket_id passed in as params. Then it uses your API key as a shared secret and signs some kind of token, which the JS library simply relays to the websocket server. The websocket server can decrypt the signed token and knows who you are. I think this works pretty well.

http://pusherapp.com/docs/private_channels

maccman commented 14 years ago

Yes, we had this feature in the previous Juggernut server (http auth callbacks) - I haven't included it yet in this one as we had performance issues - I aiming to keep Rails and the Node server totally asynchronous. At the moment I think you should just use security through obscurity - just have a unique random id as a channel name.

Alternatively - there are callbacks when clients connect/disconnect, and you can pass meta data with those callbacks. Using that, you can detect when somebody has attached to a channel they weren't meant to (hopefully a very rare event) and stop broadcasting to that channel. I know it's not ideal.

I'll look into PlasticLizard's suggestion, a signed shared secret might be a good way to do it.

ryenski commented 13 years ago

What about using Rails' authenticity token?

aaronshaf commented 13 years ago

Some sort of ability to use tokens for authentication (without the need for using Ruby) would be great. Even crucial. Obscurity isn't a good solution when you don't want a logged-out user still able to subscribe to a channel he or she has the random channel name from a previous session.

bemurphy commented 13 years ago

If callbacks are not performant, what about middle ground? Say, a randomized signature token (using HMAC, or SHA1 with shared secret) good for 60 seconds, set in an X-Header, that's used in the subscribe chain. If the token passes, proceed with the subscription, otherwise, don't add the subscription.

edit: ahem, sorry, I just ready the code and realized it's just a socket by that point so the x-header would be out. But a shared secret could still work. I might implement it on my own and let you know of my experience. Thanks.

ryanb commented 13 years ago

@bemurphy, is my new private_pub project along the lines of what you were thinking?

Currently this only works with Faye, but I've been considering making a Juggernaut adapter. Check out this example project to see it in action.

bemurphy commented 13 years ago

@ryanb Funny you should ask, I shoehorned it in to a local juggernaut over the weekend and got it working, then read your project readme a little later. Yeah, pretty much what you've got with data-signature is what I was thinking.

jagthedrummer commented 13 years ago

Just curious if there's been any progress on creating true private channels in Juggernaut. I'm about to have to start looking into getting something to work and I don't want to reinvent the wheel if someone has already made some progress down this path. I'd rather help contribute to a solution that will be good for everyone.

Thanks, Jeremy

fwoeck commented 13 years ago

As an aside: were using jugg2 in our new project in connection with a rails3 instance. Every user gets a private "channel-token" for every session that is governed by the rails-server. This is the channel the user subscribes to and it is not distributed to others. In combination with SSL, we regard this as secure enough. All client-side initiated communication goes via ajax directly to the server, and pushes from the server to the client go through jugg2. --Frank

snelson commented 13 years ago

Frank, do you never have two users listening to the same channel then? I suppose it depends on your needs, but what do you need to broadcast to multiple users? Are you just doing multiple Juggernaut.publish for each user in rails?

snelson commented 13 years ago

Didn't consider publishing to multiple channels ala Juggernaut.publish(['user-token1','user-token2'] ... is that what you're doing Frank?

fwoeck commented 13 years ago

Hi snleson,

do you never have two users listening to the same channel then? yup, every user get his own "private" channel that is created new on each session start. This is a kind of abuse of the pubsub system, but it works.

publishing to multiple channels ala Juggernaut.publish(['user-token1','user-token2'] exactly - performance wise, this isn't even a problem when you push to a thousand channels at once

--Frank

snelson commented 13 years ago

I like it ... what happens when they have multiple sessions (like two browsers open)?

fwoeck commented 13 years ago

Aas the channel token is newly generated on every login, only the last opened window will get updates. We thought about this and decided that this is rather a feature than a flaw ;)

maccman commented 13 years ago

Although if you have two windows open, they'll have the same session, both been logged in, and the same channel token.

I use a similar method, but with the users ID - this is a GUID so it isn't guessable.

On Tue, Apr 26, 2011 at 9:45 PM, fwoeck < reply@reply.github.com>wrote:

Aas the channel token is newly generated on every login, only the last opened window will get updates. We thought about this and decided that this is rather a feature than a flaw ;)

Reply to this email directly or view it on GitHub: https://github.com/maccman/juggernaut/issues/14#comment_1062970

Alex MacCaw

(415) 513-0975 @maccman

http://alexmaccaw.co.uk | http://www.leadthinking.com | http://socialmod.com

fwoeck commented 13 years ago

Yes, it depends on the actual application I think. We produce a collaboration tool, were group members see other members as avatars and these users are hold in the browser as backbonejs object including their backend ids. So this information is not private anymore and not usable as token in our case.

The other problem with multiple open windows is, that you have to synchronize user triggered actions to every active window of a user, which takes probably more effort than just reacting on a user interaction locally and propagating screen updates to the related users.

ryanb commented 13 years ago

Why not factor the current time into the private token which will make each one unique? It will then not be dependent upon the user's current session and allows multiple browser windows open. It also allows you to auto-expire it after a certain amount of time. This is what I do in the private_pub library (it looks like links with underscores currently aren't working here so you'll have to fix the URL).

fwoeck commented 13 years ago

@ryanb when I get it right, the private token in private_pub requires to be checked by the server - like e.g. the csrf-token in a rails form, right? The method of user-exclusive pubsub-channels is attractive, because it doesn't require adittional checks. If you avoid the channels beeing sniffed by others (ssl) it require only to stop sending on that channel if a server wants a specific client to be banned. The client would have to re-authorize via standard login to get a new channel token.

ryanb commented 13 years ago

@fwoeck, correct, each subscribe call passes in a signature which is unique to that specific request/channel. This needs to be authorized on the server side, but is done through a Faye extension so it all happens behind the scenes and the Rails app isn't involved.

I see now both approaches have their pros and cons. The user token is simpler up front but leaves the app to manage the which users should receive which messages. The unique signature approach requires an extra check up-front, but the app then doesn't need to be concerned about authorizing which messages go to which user.

Also good point about banning clients. If a user logs-out it's still possible to listen to messages if they have the signature and it hasn't yet expired.

bemurphy commented 13 years ago

My node-fu is pretty weak, but I took a rough stab at this by adding subscription tokens. My commit is at https://github.com/bemurphy/juggernaut/commit/f4838d91f3075c60de7b05543d369db3b5782879 so you can see if I'm on a worthwhile track.

The client passes a token that is sha1(sharedSecret:channelName:timestamp) to the server. The server verifies the signature is proper and recent enough, as well as tracks used signatures in redis for a short time to prevent reuse. Also, I'll probably want to go back and fix up the client subscription to allow for subscriptions sans tokens.

Personally I'd prefer a token approach over unique channel names per user. Thinking about a group chatroom where everybody is on a different channel name seems unintuitive to me.

EDIT: just dawned on me btw, that limiting a signature to a single use could be dangerous. I have a hunch it would break reconnects, gotta go sift through that code...

pselden commented 13 years ago

I need to add some validation on the Rails side when a user subscribes to a channel, and disconnect the user from the channel if the auth fails. Is this possible with the juggernaut gem? Looking through the examples/code, I see that you can check when the user subscribes/unsubscribes, but can you actually do anything with it other than track it?

fwoeck commented 13 years ago

@pselden4 afaik it's not possible to enforce a disconnection from within ruby - you would have to stop sending to that channel.

josem commented 13 years ago

Hello,

first time I write here :), I'm working with juggernaut and I have the same question about security, how we can control the people who subscribe to the channels.

I've read/seen 3 ways:

To sum up, I've already implemented the first way but I don't see how I can do it better and make the channels truly private, has someone figured out a better way?

Thanks in advance.

FewKinG commented 12 years ago

I ran into the same issue but didn't want to switch to faye and private_pub, because I already had all the juggernaut stuff up and running nicely. Therefore I made a bunch of changes to the juggernaut code to allow for an easy authentication mechanism and some additional new features. If you are interested, check out this commit: https://github.com/FewKinG/juggernaut/commit/f6efd8fa0b0e1bcdceebf6537750f05b6225f9ce

and read the corresponding readme chapter (at the very bottom): https://github.com/FewKinG/juggernaut/blob/master/README.md