My code is listed below but I wanted to explain my thought process and have someone correct me at every point because I have been struggling to try and get this done the RIGHT way.
I've been struggling with this for some time(5days+) and I have not found a straight forward way to do this across the web.
So I have 2 separate node apps running. One running just express-js and another running a websocket server. I'm probably just a complete knucklehead with this, but here goes.
I setup a mongo session store. When a user successfully authenticates, the session gets created and I can re-direct the user to the logged in page. While the session lives, when the user hits the 'auth-page' I can just auto redirect the user to the 'logged in page'.
Now my understanding is, when the session gets created in the mongo-store, a cookie gets created on the web browser and it is this cookie that gets to the server for each request the page makes and express-js will nicely handle the magic internally for me and I can use something like
app.post('/', function (req, res) {
}
Where the req variable gets populated with the session id by express, because express got the cookie and decoded it.
This next part is where things are dicey and any suggestions in anyway will be a huge help.
What i'm wanting to do is, inside my app.post('/'...etc) is redirect to another page. This page loads a client which initiates a websocket connection to my websocket server and my websocket server is able to use this same session-id.
So here's the thing. My express-js http server runs as a separate process with its own port and my websocket server runs as a separate process with its own port as well. After doing enough research online, I found out many sources which indicated that, when my browser makes the connection to my websocket server it will send the cookie in the header somewhere to my websocket server. So in the browser, I have some javascript code that runs:
let clientSocket = new WebSocket("ws://socket.server.address:5005");
So then from my node websocket server, I can parse out the socket.upgradeReq.headers , get the cookie, and use that to get the session id and i'm in business. That describes what I've attempted to achieve below in my code. I have been successful doing this, however I've hit different issues when trying to parse the cookie.
Sometimes I get a single cookie & sometimes, I get multiple cookies taking the form
cookie_name1=cookie_value1;cookie_name2=cookie_value2;
cookie_name3=cookie_value3;cookie_name4=cookie_value4;
cookie_name5=cookie_value5;
Sometimes I get a single cookie & sometimes, I get multiple cookies taking the form
question 1 - why do I get multiple cookies being sent to my websocket server? Is that dictated strictly by the browser? What can I do about that if anything?
question 2 - Will the cookies ALWAYs come in that format? I would hate for the semicolon delimiter style to change and that break my code
question 3 - upon reviewing my code, my thought process can you suggest and guide me with a complete different/better implementation to achieve this? Can you suggest I change parts? My goal is to be able to spin up multiple different websocket servers & webservers and load-balance between them. I'm trying to find a reliable way to do this so that my code doesn't break... my node apps are just very frail, some direction would help. It seems like for nodejs, despite its maturity in 2017, good information lives only on stackoverflow,github issue threads and irc.freenode and I classify some of these things as basic...
packages and versions used
web-server package versions
---------------
express#4.15.2
express-session#1.15.2
mongodb#2.2.26
cookie-parser#1.4.3
body-parser#1.17.1
connect-mongodb-session#1.3.0
socket-server package versions
---------------
uws#0.14.1
below is my code
webserver.js
'use strict';
const bodyparser = require('body-parser');
const cookieparser = require('cookie-parser');
const express = require('express');
const app = express();
const express_session = require('express-session');
const connect_mongo = require('connect-mongodb-session')(express_session);
const port = process.env.NODE_WEBSERVER_PORT;
const _ = require('underscore');
const mongo_store = new connect_mongo({
uri: 'mongodb://mongo1.weave.local:27017/sessiondb',
collection: 'sess'
});
const session_time = 1000 * 60 * 5 ; // 5 minute(s)
app.use(express_session({
secret: 'superman',
cookie: {
maxAge: session_time,
httpOnly: false
},
store: mongo_store,
resave: false,
saveUninitialized: false,
name: 'inspect_the_deq',
httpOnly: false
}));
app.use(bodyparser.urlencoded({ extended: false }))
app.use(bodyparser.json());
app.set('view engine', 'pug');
app.set('views', __dirname+'/pugs')
app.use(express.static(__dirname + '/com/js'));
app.use(express.static(__dirname + '/com/asset'));
const mongo = require('mongodb').MongoClient;
const mongo_url = 'mongodb://mongo1.weave.local:27017/main';
let account = null;
let database = null;
mongo.connect(mongo_url, function(err, db) {
let collection = db.collection('account');
account = collection;
database = db;
});
app.get('/', function (req, res) {
if(req.session.user){
const user = req.session.user;
res.render('main', {message: 'user '+user+' logged in' });
console.log('session found logging you on');
}else{
res.render('login', {message: 'Login'});
console.log('no session exists');
}
});
app.post('/', function (req, res) {
const user = req.body.username, pass = req.body.password;
const seconds = session_time;
account.findOne({username: user, password: pass }, function(err, document) {
if( document !== null ){
req.session.user = user;
req.session.cookie.expires = new Date(Date.now() + seconds);
req.session.cookie.signed = true;
res.render('main', {message: 'user '+user+' logged in'});
console.log('some id is '+req.session.id);
console.log('cookie id is '+req.session.cookie);
console.log('sess id is '+req.sessionID);
}else
res.render('login', {message: 'Login', login_error: 'invalid username or password'});
});
});
app.listen(port, function () {
console.log('http server '+port);
});
Socket Server code here
'use strict';
const _ = require('underscore');
const uwsPlugin = require('uws').Server;
const socket_port = process.env.NODE_SOCKET_PORT;
const ws = new uwsPlugin({ port: socket_port, maxPayload: 0 });
//const Meepack = require('./core/meepack');
const cookieparser = require('cookie-parser');
const express_session = require('express-session');
const connect_mongo = require('connect-mongodb-session')(express_session);
const mongo_store = new connect_mongo({
uri: 'mongodb://mongo1.weave.local:27017/sessiondb',
collection: 'sess'
});
ws.on('connection', function connection(socket) {
'use strict';
console.log('client verification process ');
let headers = Object.keys(socket.upgradeReq.headers);
let upgradeReq = Object.keys(socket.upgradeReq.headers.cookie);
let cookie = socket.upgradeReq.headers.cookie;
//use the cookie here to get the session_id and do whatever you want
socket.on('close', function close(e) {
console.log('connection closed');
});
socket.on('message', function close(data) {
'use strict';
});
});
Related
I'm trying to use a SERN stack to create a login/ registration system, using express-session to determine if the user logged in. The cookie is being saved into the browser, and is sent to the session store with the cookie being modified, but is unable to be accessed through other routes. Despite the cookie being present in the browser and store, it continues to generate new sessions in other API calls. The server is hosted on port 5000, while the react client is hosted on 3000. Clientside calls are made with axios, and are done with the withCredentials:true setting
db.js:
`
const session=require("express-session");
const config={
connectionLimit:process.env.DB_CONNECTION_LIMIT,
host:process.env.DB_HOST,
user:process.env.DB_USER,
password:process.env.DB_PASSWORD,
database:process.env.DB_DATABASE
}
const db=mysql.createPool(config);
const SQLStore=require("express-mysql-session")(session);
const store=new SQLStore({
expiration:7*24*3600*1000,
createDatabaseTable:true,
schema:{
tableName:"Sessions",
columnNames:{
session_id:"session_id",
expires:"expires",
data:"session_data"
}
}
},db);
server.js
const express=require('express');
const app=express();
const cors=require('cors');
app.use(cors({
origin:"http://localhost:3000",
methods:["GET,POST"],
credentials:true
}));
const bodyParser = require('body-parser')
app.use(bodyParser.urlencoded({extended:true}));
app.use(bodyParser.json());
require('dotenv').config()
const dbExports=require('./databaseConnect');
const session=dbExports.session;
const store=dbExports.store;
const searchUserByInsertID=dbExports.searchUserByInsertID;
const searchUserByUsername=dbExports.searchUserByUsername;
const searchUserByUsernameAndEmail=dbExports.searchUserByUsernameAndEmail;
const createUser=dbExports.createUser;
const cookieParser=require("cookie-parser");
app.use(cookieParser());
app.use(session({
name:process.env.SESSION_NAME,
resave:false,
saveUninitialized:false,
store:store,
secret:process.env.SESSION_SECRET,
cookie:{
secure:false,
expires:14*24*60*60*1000
}
}))
const bcrypt=require('bcrypt');
const validator=require("email-validator")
require("./routes")(app,validator,bcrypt,searchUserByInsertID,searchUserByUsername,searchUserByUsernameAndEmail,createUser);
app.listen(5000,()=>{
console.log("listening on port 5000")
})
`
Routes.js:
app.post('/login',async (req,res)=>{
const username=req.body.username;
const password=req.body.password;
const user=await searchUserByUsername(username);
if(!user.length){
console.log("username not found in db");
res.json({status:"usernameInvalid"});
}
else{
const dbHashedPassword=user[0].password;
if(await bcrypt.compare(password,dbHashedPassword)){
req.session.user=user[0];
req.session.save();
console.log(req.session);
res.json({status:"userValid"});
}
else{
console.log("wrong password");
res.json({status:"passwordInvalid"});
}
}
})
//once the user is redirected to a landing page after registering/logging in, this route is called, but returns undefined, and console logging the sessionid shows a different value than the one that is currently in the browser cookie/store.
app.get('/checkSession',async (req,res)=>{
console.log(req.session.user);
res.json({status:"hi"});
})
api call in react client:
Axios.defaults.withCredentials=true;
Axios.get('http://localhost:5000/checkSession').then((response)=>{
console.log(response.data);
}).catch((err)=>{
console.log(err);
})
browser session is created
session store also receives the session
I tried modifying session settings, but I was unable to continuously access the session through different routes. Once the client makes a subsequent request, despite having the session cookie present in browser, it is unable to be accessed by the server.
I am building a single page application with a Vuejs frontend and a Nodejs backend. Been reading tons about single sign on and oidc, and managed to implement authentication using Oidc for the frontend, where I get a token from my identity provider.
Not sure however, now, how to also implement this for the backend and where/when/how.
So currently, when a user accesses the page, in my router.js file, this happens:
router.beforeEach(vuexOidcCreateRouterMiddleware(store));
In the store then, I do this:
Vue.use(vuex);
const store = new Vuex.Store({
state: {
// holds current list of products
products: [],
},
getters,
mutations,
actions,
modules: {
// initialize PING-OIDC module
oidcStore: vuexOidcCreateStoreModule(
oidcSettings,
{ namespaced: false },
{
userLoaded: (oidcUser) => {
axios.defaults.headers.common.Authorization = `${oidcUser.token_type} ${oidcUser.access_token}`;
},
},
),
},
});
export default store;
So I set the authorization header, but now I am not sure where and how to proceed in the backend to also add + validate authentication there.
Very new to all this and there seem to be so many different way to proceed, so appreciate all the hints.
Currently, in server.js, I just do the following:
var express = require('express');
var app = express();
var bodyParser = require('body-parser');
app.use(bodyParser.json())
const cors = require('cors')
const corsOptions = {
origin: 'http://localhost:5002',
optionsSuccessStatus: 200
}
app.use(cors(corsOptions))
//get mysql db here
const data = require('./app/config/db.config.js');
const db = data.MySQL_DB;
// show all products
app.get('/api/productlist',(req, res) => {
const sql = "SELECT ID FROM Product_Table";
const query = db.query(sql, (err, results) => {
if(err) throw err;
console.log("productIds ", results);
res.send(JSON.stringify({"status": 200, "error": null, "response": results}));
});
});
// Create a Server
var server = app.listen(8080, function () {
var host = server.address().address
var port = server.address().port
console.log("App listening at http://%s:%s", host, port)
})
So should I create a post request in there as well? Just not sure on how to validate the request there.. Thanks a lot for the help!
You need to clarify which data/routes/pages you want to protect.
You log in from your client -> send to the server (ex : api/login) -> respond to your client with credential -> store the user and credential
see passport.js for express
see accesscontrol for your data server side
Note that routes in client can be easily hack
const express = require("express");
const app = express();
var i = new Number;
i=0;
app.get("/", function(req, res){
i++
console.log(i);
});
app.listen(8080);
I created a very small node js project. I have a problem. when I create a variable like above, it doesn't evaluate for each user separately. that is, when a user requests a get, I want it to be 1 each time.
Sample
my problem is that when a jack user enters a site, if he doesn't log out, someone who enters the site's home page from another device enters his account with jack.
how can I do that?
The simplest answer for your question is to simply declare and increment the variable inside the function passed to app.get, but I'm going to assume that you would like a situation where, for a given user's series of requests, the number will increment.
The simplest way to do this is using a server side session, which is provided by the express-session library. Additionally, in order for this to work, you need to call res.end() in order to send the cookie associated with the server session back to the user's browser. More information on sessions generally can be found here.
Below is code to replicate the intent of what you have there, but incrementing for each request from a unique browser instance (identified by the same cookie value associated with the server session):
const express = require('express');
const session = require('express-session');
const app = express();
app.use(session({
resave: false,
saveUninitialized: true,
secret: 'secret',
cookie: {
maxAge: 60000
}
}));
app.get('/', function(req, res){
if (!req.session.value) {
req.session.value = 0;
}
req.session.value++;
console.log(req.session.value);
res.end();
});
app.listen(8080);
I've been having problems trying to access stored session values! Once I've set the values and try access them from a new route, I get undefined! So basically I've got a login (POST) and in that request I set the session data, and then I have a show user details (POST) where I try and access the session data I've just stored.
Setup
// Setup express and needed modules #############################################
var express = require('express'),
session = require('express-session'),
cookieParser = require('cookie-parser'),
redis = require("redis"),
redisStore = require('connect-redis')(session),
bodyParser = require('body-parser');
var client = redis.createClient(), //CREATE REDIS CLIENT
app = express();
// Setup app
app.use(cookieParser('yoursecretcode'));
app.use(session(
{
secret: 'x',
store: new redisStore({
port: 6379,
client: client
}),
saveUninitialized: true, // don't create session until something stored,
resave: false // don't save session if unmodified
}
));
app.use(bodyParser.urlencoded({ extended: true}));
app.use(bodyParser.json());
app.set('trust proxy', 1) // trust first proxy
So as you've seen my setup, you know I'm using express sessions and Redis. Below is where I'm setting the session values! If I print out the session values here it works, but then If I try and access the session data in another route it returns undefined.
Routes
I send a http post request and set the session data:
router.route('/login/').post(function(req, res) {
req.session.userId = req.body.uId;
req.session.name = req.body.uName;
// THIS PRINTS OUT IF I TRY AND ACCESS THE SESSION DATA HERE
console.log("THIS PRINTS OUT --> " + req.session.name);
});
So now that the session values have been set, I can go access them right, no, I get undefined each time I try and log them out.
router.route('/user/printoutuserdetails').post(function(req, res) {
// THESE RETURN UNDEFINED
console.log(req.session.userId);
console.log(req.session.uName);
console.log("THIS PRINTS OUT --> " + req.session.name);
});
Does anyone have any idea what's happening? I've tried everything and looked everywhere and can't seem to find a way to get it to work!
Solved:
The reason this wasn't was because you're not suppose to use sessions when using a RESTFUL api.
I've got a rather strange problem with my code on production server. On my MacOS it works perfectly, but when I deploy my app, I cannot login. After debug, I've found that I cant load session from req-object. Here's the code of all main parts (settings,, login page and main page after login)
//SETTINGS
var express = require('express');
var app = express.createServer();
var mongo = require('mongodb'),
Server = mongo.Server,
Db = mongo.Db,
ObjectID = require('mongodb').ObjectID;
var BSON = require('mongodb').BSONPure;
var RedisStore = require('connect-redis')(express);
//connecting to mongo
var server = new Server('localhost', 27017, {
auto_reconnect: true
});
var db = new Db('metadocs-node_db', server);
//setting express app
app.set('view engine', 'ejs');
app.configure(function () {
app.use("/static", express.static(__dirname + '/static'));
app.use(express.bodyParser());
app.use(express.cookieParser());
app.use(express.session({
secret: "meta-meta",
store: new RedisStore
}));
app.use(express.methodOverride());
});
//login page - POST for /login page
app.post('/login', function (req, res) {
db.collection("users", function (err, collection) {
collection.findOne({
username: req.body.username
}, function (err, doc) {
if (doc && doc.password == req.body.password) {
req.session.user_id = doc._id;
res.redirect('/');
} else {
res.render('login.ejs', {
success_login: 1
});
}
});
});
});
//GET INDEX PAGE - only after login
app.get('/', loadUser, function (req, res) {
db.collection("companies", function (err, collection) {
collection.count(function (err, count) {
res.render('index.ejs', {
total_companies: count,
current_user: req.currentUser['username']
});
});
});
});
//loadUser() is function that creates/loads user session if possible
function loadUser(req, res, next) {
if (req.session.user_id) {
db.collection("users", function (err, collection) {
collection.findOne({
_id: new ObjectID(req.session.user_id)
}, function (err, user) {
if (user) {
req.currentUser = user;
next();
} else {
res.redirect('/login');
}
});
});
} else {
res.redirect('/login');
}
}
Here's the problem code line:
if (req.session.user_id) {
in loadUser() function. The problem is req.session.user_id is empty – I've found it out while debugging every line step-by-step. What I'm doing wrong? It works on my Mac, but does't work on Ubuntu.
I have tried the server you gave the address for at another post for this same problem. Cookies are set without any problems. I logged in successfully. However, one thing caught my attention: expiration time. It is 6/26/12 23:18 GMT now and your cookie expiration is set to 27 Jun 2012 03:18 GMT, which leaves only 4 hours before cookie is expired.
It is perfectly okay to set cookies that will expire in 4 hours; unless your server or a client has wrong date/time or timezone set. Could you please make sure that the working/non-working clients and of course your server have the correct date/times and time zones set up? I believe this is probably the reason of your problem.
On systems I develop, I give cookies expiration times for one year. I validate session on server side without depending on cookie expiration. I do not use connect for cookie and session management since I am always more comfortable doing these things myself. However you can do the same thing while you are still using connect. At session settings, you should set maxAge for cookie to a higher value:
Below code is copied from connect documentation. I have just added maxAge parameter on third line, setting expiration to one day.
connect().
.use(connect.cookieParser('keyboard cat'))
.use(connect.session({ key: 'sid', cookie: { secure: true, maxAge:86400000 }}))
I hope your problem is solved with this.
Here are some things I'd try:
Make sure you are not using Ubuntu 10.04 or 10.10:
Ubuntu 10.04 and 10.10 have serious bugs (especially 10.10) that cause slow downs if not just instance hangs. http://redis.io/topics/problems
Where are you configuring your redis server connection? I think by default it will look to localhost but in a production environment you typically need to specify the host/port/auth (though I'm not sure if you own the production environment or you are hosting with a 3rd party, so this may not apply.)
Try setting no_ready_check flag to true as well:
client = redis.createClient(port, host, {no_ready_check: true});
client.auth(password, function() {
console.log('Redis client connected');
});
//then pass client into expressjs like so:
...
store: new RedisStore {client: client}
Also add an event handler for 'error' and see if redis is reporting any issues:
client.on('error', function (e) {
console.log(e);
});
Lastly, I'd dump your environment vars in production and your localhost dev to see if there are any configuration differences. Sometimes you set these up in development and forget about them by the time you get to production :)
Hope that helps!
Another solution which I found was: When you are on Debian make sure that you get a different aptitude source so that you can install the most uptodate version of Redis.
This issue kept me busy searching for a few hours...