I'm wondering how to properly authenticate a user using sockets and cordova.
Currently, the following happens:
User registers or logs in using an normal ajax command, sending email and password
I send back an access token and their email, which is stored in localstorage on the phone and as email => accesstoken in redis
The user then sends a connection request to the socket.io server, something like (client side):
var socket = io('http://192.168.50.3:8080/');
socket.on('connect', function(){
socket.emit('init', { email: 'email#email.com', token: '12ueno1' });
});
I then check on the backend if that access token and email are in my redis server, if they are, I start listening for any commands sent by the client like so (server side):
var io = require('socket.io').listen(8080),
mongoose = require('mongoose'),
schema = mongoose.Schema,
userModel = require('./models/user'),
lobbyModel = require('./models/lobby'),
redis = require('redis').createClient();
io.sockets.on('connection', function(socket){
socket.on('init', function(data){
// here, we check for an init command which will have an email and access token
// if it's there, we put the user into a room and let them wait for data
redis.get(data.email, function(err, reply){
if(reply === data.token){
// get into room
var roomKey = data.email;
socket.join(roomKey);
// get user
var user;
userModel.findOne({ email: data.email }, function(err, doc){
user = doc;
});
socket.in(roomKey).on('create_lobby', function(data){
// do stuff for creating a lobby and then send back the data
io.sockets.in(roomKey).emit('created_lobby', {
email: user.email
});
});
}
});
});
});
Now authenticated, the user can send commands like (client side):
socket.on('create_lobby', { name: 'test' });
Now, this works fine, I'm just wondering if I'm going at it the right way, or if I'm creating an insecure system.
Rather late but I've only just come across this question.
The answer is that the approach is insecure.
Firstly, the token isn't being validated.
Secondly, there is no timeout check.
Related
I am using node.js, ejs and socket.io. Mysql is used for database.
What am I trying to achieve?
Lets say there are 2 users and user 2 is already logged in. When User 1 login, it will inform User 2 that User 1 is logged in.
What have I tried so far?
Below is the success callback of jquery ajax to authenticate user in the database.
success: function(result) {
//Connnecting with socket.
var clientSocket = io().connect("http://localhost:1235?Token=" + result.Data.Token);
//Once connected, emit the message in success callback that User 1 is logged in.
//Then redirecting the user to profile page in the success callback of emit.
clientSocket.on("connect", function() {
clientSocket.emit("Profile", { "Message": "User is online." }, function(receipt) {
if(receipt) {
location.href = "/profile";
}
});
});
}
So far, everything is good.
Below is the Server side code.
var socket_io = socket(server);
socket_io.on("connection", function(socketParam) {
console.log("Connected");
//Send Message to all users once the user is authenticated as requested in above ejs
socketParam.on("Profile", function(data, profileCallback) {
profileCallback(true);
console.log(data);
socket_io.emit("Profile", data);
});
});
Issue starts now in below code. Below code is present in profile page which appears once user is authenticated and is redirected to this page.
var userData = JSON.parse(localStorage.getItem("User_Data"));
clientSocket = io().connect("http://localhost:1235?Token=" + userData.Token);
clientSocket.on("connect", function() {
clientSocket.on("Profile", function(data) {
debugger;
});
});
Here the issue in above code(profile.ejs) is, when I reaches in the profile page, the connection is being re-established. This disconnects the user and reconnects and I think due to that reason, the data emitted in Profile event from server never reaches.
Question
Is there any way to maintain the connection established in client side so that there would be no need to re-establish the connection?
Please let me know if you need more info.
I am trying to register a new user through node-xmpp from node.js to an ejabberd but the authentication and registration events are not invoking neither it is not connecting.
var clientXMPP = require('node-xmpp-client');
var serverXMPP = require('node-xmpp-server');
var c2s = new serverXMPP.C2SServer({
jid: 'testuser#192.168.1.1',
password: 'test'
});
c2s.on("connect", function(client) {
console.log(client)
c2s.on('register', function(opts, cb) {
console.log('REGISTER')
cb(false)
})
c2s.on("authenticate", function(opts, cb) {
console.log("AUTH" + opts.jid + " -> " +opts.password);
cb(false);
});
c2s.on('disconnect', function () {
console.log('DISCONNECT')
})
});
How can I register a new user and authenticate them in node-xmpp.
If you just need to create new user from nodejs to ejabberd server please make sure you have in-band registration enabled in ejabberd.
you don't need node-xmpp-server, you can only us node-xmpp-client. Client library will help you to connect with ejabberd admin user.
here is an example
const Client = require('node-xmpp-client');
let admin = new Client({
jid: '{adminJID}',
password: '{adminPassword}'
});
// check for errors to connect with socket
admin.connection.socket.on('error', function (error) {
// handle error
});
admin.on('online', function (data) {
// Register stanza
let stanza = new Client.Stanza('iq', {type: 'set', id: 'reg1', to: '192.168.1.1'})
.c('query', {xmlns: 'jabber:iq:register'})
.c('username').t('{username}').up() // Give a new username
.c('password').t('{password}') // Give a new user's password
admin.send(stanza); // send a stanza
});
// response stanzas
admin.on('stanza', function (stanza) {
// check for error/success
});
Yet another "I've just started learning node.js/express and now I stuck" person here. I've tried looking everywhere for answers but I've reached a dead end. This is my first Stackoverflow question, so please tell me if I have done something wrong or unconventional.
I'm trying to create a POST request that saves a User-object to an express-session (on MongoDB), and redirects you to a URL that handles your session information.
The problem is, that the user is redirected before the header is set, and I am given the following:
Error: Can't set headers after they are sent.
Here is my code. I know it's a lot.. Im sorry.
router.post()
// Handler for POST requests from root page (/)
router.post('/', function(req, res) {
console.log("Router: A POST request for: \"/\" has been called!");
var username = req.body.username;
var password = req.body.password;
// Connect to database
mongoose.connect(dbAddress);
var db = mongoose.connection;
// Report if error in connection
db.on('error', console.error.bind(console, 'Database: Connection error..'));
// Callback function on 'open'
db.once('open', function() {
console.log("Database: Connection successful!");
console.log("Database: User supplied username: " + username);
console.log("Database: User supplied password: " + password);
// Find User object with a userId of req's username
User.findOne({ 'userId' : username.toUpperCase() }, function(err, userObj) {
if (err)
return console.err(err);
// Disconnect when done retrieving user object
mongoose.disconnect();
if ( userObj ) {
console.log("Database: Returned password from MongoDB:");
console.log(userObj.password);
var db_password = userObj.password;
if (password === db_password) {
console.log("Database: User Authenticated");
// Set 'req.session.user' as cookie
req.session.user = userObj;
// Redirect to homepage, with new cookie set
res.redirect('/');
} else { // If passwords don't match
res.render('index', {
showError: true
});
}
} else { // If userObj is null
res.render('index', {
showError: true
});
}
});
});
});
Note the 'req.session.user = userObj' part. Here I am trying to set 'user' in the session to the user object retrieved from MongoDB. In the next line, I am redirecting the user back to the GET request handler for '/' which handles the user based on the session information.
For some reason, these aren't happening in order. The GET request handler doesn't find req.session.user.
I understand that node.js is all about asynchronisation, but from other examples I've found online, this is supposed to work. What am I missing here?
You could put your redirect inside a callback after the session is saved e.g:
...
// Set 'req.session.user' as cookie
req.session.user = userObj;
req.session.save(function(err) {
// session saved
res.redirect('/')
})
...
Hopefully this will make sure that the user is only redirected after the session is saved.
Note: Make sure you hash your password with something like Bcrypt or pbkdf2.
Im building a chat application in node.js , socket.io and mongoose. My goal is to build a one on one chat, it is similar like facebook web chat. So basically whenever I click on a user it will go to that user url and possibly chat with him/her
But the problem, that I'm facing is that I could only emit the message to the targeted user, but not to myself.
Here's the current code
Serverside
socket.on('chatTo', function(data){
User.findOne({ username: data.destinationUsername, socketId: { '$ne': null}}, function(err, foundUser) {
if (foundUser) {
io.to(foundUser.socketId).emit('incomingChat', { sender: user.username, message: data.message });
} else {
io.to(socket.id).emit('deliverError', { error: foundUser + ' is not online' });
}
});
});
Clientside
$(function() {
var socket = io();
function chatTo(message, destinationUsername) {
socket.emit('chatTo', { message: message, destinationUsername });
}
$('#sendMessage').submit(function(){
var input = $('#message').val();
var username = $('#username').val();
chatTo(input, username);
$('#message').val('');
return false;
});
socket.on('incomingChat', function(data) {
var html = data; // Messages to append to media-list
$('.media-list').append(html);
});
});
So what is happening here is that, on the clientside, User A clicks form submit , to submit the message, it will invoke chatTo function and emit the data. Just want to let you guys know, input and username are from html page. It would look something like this
input = "Hello";
username = "jackmoscovi" // user's username
then it will emit both of these data back to server, which is socket.on('chatTo') is listening to. After some MongoDB operation, if found the user then emit to that specific socket.
The result will be
The message that was emitted by batman only shows up on joker's page but not on Batman's page. I know that because I specifically emit to that socket. The real question is How do I force both of these user to be in a unique room? Should I create a new Room mongoose model? and then call socket.join('uniqueRoom')?
and then simply io.to('uniqueRoom').emit("Message?");
How would I implement this room mechanism?
I have been struggling for 2 days already, please someone end this misery :(
First create a mongoose schema like this
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var messageSchema=new Schema({
sender : {
type : String,
},
receiver : {
type : String.
},
message : {
type : String
}
})
mongoose.model('messages',messageSchema);
Then in server code
message = mongoose.model('messages')
socket.on('chatTo', function(data){ // data is json containing sender, receiver and message
User.findOne({ username: data.destinationUsername, socketId: { '$ne': null}}, function(err, foundUser) {
if (foundUser) {
io.to(foundUser.socketId).emit('incomingChat', { sender: user.username, message: data.message });
socket.emit('incomingChat', { sender: user.username, message: data.message });
var newMessage = new message({
sender : data.sender,
receiver : data.receiver,
message : data.message
})
newMessage.save(function (err, data){
if(err)
console.log(err)
})
} else {
io.to(socket.id).emit('deliverError', { error: foundUser + ' is not online' });
}
});
});
This is a little bit of a difficult one to answer because it feels like you are actually asking the wrong question. To understand how you are "supposed" to do this you really have to have solved quite a few other architectural issues first.
The first thing you need to solve is the Authentication/Authorisation problem i.e. when you connect a socket from the browser to the server how does your server know who you are and know that you're not an imposter? I don't have a best practice for this but you might want to check out the socketio-auth package which might give you a few ideas.
The second thing you need to solve is how to associate the authenticated user with the socket. The only source that I could find that has any reference to this is in the chat demo on the socket.io website.
At this point, you should have a collection of sockets in your server that have associated users. When Batman comes online you should check the database for any Chats that he is part of and have him connect to that chat room by id:
Chat.find({usernames: "Batman"}).exec().then(function(chats){
chats.forEach(function(chat){
socket.join(chat.id);
})
});
So this so far is assuming that Joker hasn't already created a chat with Batman, and hence there is not a chat in the database that connects them both.
When Joker wants to chat to batman you need to send a "connect" message down the socket that
creates a chat with both of them in it
finds both their current sockets if they are currently connected to the server and
sends a message that they have connected to each other
example:
socket.on('startChat', function(data){
//assuming that you have assocated the current user with the socket
var currentUser = socket.user;
//who the current user (Joker) wants to connect to
var connectToUser = data.connectToUser; // Batman
Chat.create({
usernames: [currentUser.username, connectToUser.username] // ["Joker", "Batman"]
}).then(function(newChat){
socket.join(newChat.id); // add current user (Joker) to that socket.io room
// this is overly simplified: assuming you have an array collectionOfConnectedSockets which
// contains all currently connected sockets
var otherUserSocket = collectionOfConnectedSockets[connectToUser.username];
//otherUserSocket now is Batman's socket - so make him join the room
otherUserSocket.join(newChat.id);
//send them both a message that a chat has been connected
io.to(newChat.id).emit('You have been connected to a new chat');
});
});
Now this brushes over a lot of the details, and each of those details depend on your application's architecture. Also my syntax may be a little off, the examples are only given as an indication of you should be trying to do and not as a direct solution to your problem.
What is the best way (most secure and easiest) to authenticate a user for a server side route?
Software/Versions
I'm using the latest Iron Router 1.* and Meteor 1.* and to begin, I'm just using accounts-password.
Reference code
I have a simple server side route that renders a pdf to the screen:
both/routes.js
Router.route('/pdf-server', function() {
var filePath = process.env.PWD + "/server/.files/users/test.pdf";
console.log(filePath);
var fs = Npm.require('fs');
var data = fs.readFileSync(filePath);
this.response.write(data);
this.response.end();
}, {where: 'server'});
As an example, I'd like to do something close to what this SO answer suggested:
On the server:
var Secrets = new Meteor.Collection("secrets");
Meteor.methods({
getSecretKey: function () {
if (!this.userId)
// check if the user has privileges
throw Meteor.Error(403);
return Secrets.insert({_id: Random.id(), user: this.userId});
},
});
And then in client code:
testController.events({
'click button[name=get-pdf]': function () {
Meteor.call("getSecretKey", function (error, response) {
if (error) throw error;
if (response)
Router.go('/pdf-server');
});
}
});
But even if I somehow got this method working, I'd still be vulnerable to users just putting in a URL like '/pdf-server' unless the route itself somehow checked the Secrets collection right?
In the Route, I could get the request, and somehow get the header information?
Router.route('/pdf-server', function() {
var req = this.request;
var res = this.response;
}, {where: 'server'});
And from the client pass a token over the HTTP header, and then in the route check if the token is good from the Collection?
In addition to using url tokens as the other answer you could also use cookies:
Add in some packages that allow you to set cookies and read them server side:
meteor add mrt:cookies thepumpinglemma:cookies
Then you could have something that syncs the cookies up with your login status
Client Side
Tracker.autorun(function() {
//Update the cookie whenever they log in or out
Cookie.set("meteor_user_id", Meteor.userId());
Cookie.set("meteor_token", localStorage.getItem("Meteor.loginToken"));
});
Server Side
On the server side you just need to check this cookie is valid (with iron router)
Router.route('/somepath/:fileid', function() {
//Check the values in the cookies
var cookies = new Cookies( this.request ),
userId = cookies.get("meteor_user_id") || "",
token = cookies.get("meteor_token") || "";
//Check a valid user with this token exists
var user = Meteor.users.findOne({
_id: userId,
'services.resume.loginTokens.hashedToken' : Accounts._hashLoginToken(token)
});
//If they're not logged in tell them
if(!user) return this.response.end("Not allowed");
//Theyre logged in!
this.response.end("You're logged in!");
}, {where:'server'});
I think I have a secure and easy solution for doing this from within IronRouter.route(). The request must be made with a valid user ID and auth token in the header. I call this function from within Router.route(), which then gives me access to this.user, or responds with a 401 if the authentication fails:
// Verify the request is being made by an actively logged in user
// #context: IronRouter.Router.route()
authenticate = ->
// Get the auth info from header
userId = this.request.headers['x-user-id']
loginToken = this.request.headers['x-auth-token']
// Get the user from the database
if userId and loginToken
user = Meteor.users.findOne {'_id': userId, 'services.resume.loginTokens.token': loginToken}
// Return an error if the login token does not match any belonging to the user
if not user
respond.call this, {success: false, message: "You must be logged in to do this."}, 401
// Attach the user to the context so they can be accessed at this.user within route
this.user = user
// Respond to an HTTP request
// #context: IronRouter.Router.route()
respond = (body, statusCode=200, headers) ->
this.response.statusCode statusCode
this.response.setHeader 'Content-Type', 'text/json'
this.response.writeHead statusCode, headers
this.response.write JSON.stringify(body)
this.response.end()
And something like this from the client:
Meteor.startup ->
HTTP.get "http://yoursite.com/pdf-server",
headers:
'X-Auth-Token': Accounts._storedLoginToken()
'X-User-Id': Meteor.userId()
(error, result) -> // This callback triggered once http response received
console.log result
This code was heavily inspired by RestStop and RestStop2. It's part of a meteor package for writing REST APIs in Meteor 0.9.0+ (built on top of Iron Router). You can check out the complete source code here:
https://github.com/krose72205/meteor-restivus
Because server-side routes act as simple REST endpoints, they don't have access to user authentication data (e.g. they can't call Meteor.user()). Therefore you need to devise an alternative authentication scheme. The most straightforward way to accomplish this is with some form of key exchange as discussed here and here.
Example implementation:
server/app.js
// whenever the user logs in, update her apiKey
Accounts.onLogin(function(info) {
// generate a new apiKey
var apiKey = Random.id();
// add the apiKey to the user's document
Meteor.users.update(info.user._id, {$set: {apiKey: apiKey}});
});
// auto-publish the current user's apiKey
Meteor.publish(null, function() {
return Meteor.users.find(this.userId, {fields: {apiKey: 1}});
});
lib/routes.js
// example route using the apiKey
Router.route('/secret/:apiKey', {name: 'secret', where: 'server'})
.get(function() {
// fetch the user with this key
// note you may want to add an index on apiKey so this is fast
var user = Meteor.users.findOne({apiKey: this.params.apiKey});
if (user) {
// we have authenticated the user - do something useful here
this.response.statusCode = 200;
return this.response.end('ok');
} else {
// the key is invalid or not provided so return an error
this.response.statusCode = 403;
return this.response.end('not allowed');
}
});
client/app.html
<template name="myTemplate">
{{#with currentUser}}
secret
{{/with}}
</template>
Notes
Make /secret only accessible via HTTPS.
While it's very likely that the user requesting /secret is currently connected, there is no guarantee that she is. The user could have logged in, copied her key, closed the tab, and initiated the request sometime later.
This is a simple means of user authentication. I would explore more sophisticated mechanisms (see the links above) if the server-route reveals high-value data (SSNs, credit cards, etc.).
See this question for more details on sending static content from the server.
I truly believe using HTTP headers are the best solution to this problem because they're simple and don't require messing about with cookies or developing a new authentication scheme.
I loved #kahmali's answer, so I wrote it to work with WebApp and a simple XMLHttpRequest. This has been tested on Meteor 1.6.
Client
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
// Skipping ahead to the upload logic
const xhr = new XMLHttpRequest();
const form = new FormData();
// Add files
files.forEach((file) => {
form.append(file.name,
// So BusBoy sees as file instead of field, use Blob
new Blob([file.data], { type: 'text/plain' })); // w/e your mime type is
});
// XHR progress, load, error, and readystatechange event listeners here
// Open Connection
xhr.open('POST', '/path/to/upload', true);
// Meteor authentication details (must happen *after* xhr.open)
xhr.setRequestHeader('X-Auth-Token', Accounts._storedLoginToken());
xhr.setRequestHeader('X-User-Id', Meteor.userId());
// Send
xhr.send(form);
Server
import { Meteor } from 'meteor/meteor';
import { WebApp } from 'meteor/webapp';
import { Roles } from 'meteor/alanning:roles'; // optional
const BusBoy = require('connect-busboy');
const crypto = require('crypto'); // built-in Node library
WebApp.connectHandlers
.use(BusBoy())
.use('/path/to/upload', (req, res) => {
const user = req.headers['x-user-id'];
// We have to get a base64 digest of the sha256 hashed login token
// I'm not sure when Meteor changed to hashed tokens, but this is
// one of the major differences from #kahmali's answer
const hash = crypto.createHash('sha256');
hash.update(req.headers['x-auth-token']);
// Authentication (is user logged-in)
if (!Meteor.users.findOne({
_id: user,
'services.resume.loginTokens.hashedToken': hash.digest('base64'),
})) {
// User not logged in; 401 Unauthorized
res.writeHead(401);
res.end();
return;
}
// Authorization
if (!Roles.userIsInRole(user, 'whatever')) {
// User is not authorized; 403 Forbidden
res.writeHead(403);
res.end();
return;
}
if (req.busboy) {
// Handle file upload
res.writeHead(201); // eventually
res.end();
} else {
// Something went wrong
res.writeHead(500); // server error
res.end();
}
});
I hope this helps someone!
Since Meteor doesn't use session cookies, client must explicitly include some sort of user identification when making a HTTP request to a server route.
The easiest way to do it is to pass userId in the query string of the URL. Obviously, you also need to add a security token that will prove that the user is really who the claim they are. Obtaining this token can be done via a Meteor method.
Meteor by itself doesn't provide such mechanism, so you need some custom implementation. I wrote a Meteor package called mhagmajer:server-route which was thoroughly tested. You can learn more about it here: https://blog.hagmajer.com/server-side-routing-with-authentication-in-meteor-6625ed832a94