How to separate nodejs sessions - node.js

I have a very simple nodejs chatbot that learns things about a client that is connected to it. For example, if a client says "My name is Bob" and then asks the chatbot "What is my name?" the chatbot will say "Bob" the problem is that if another client connects to the server and then asks the chatbot the same question, the chatbot will also reply with "Bob". I understand I need to implement some way of dealing with sessions but everything I found online about sessions was explaining things about cookies and how to store user authentication.
All I want to do is let each client have his own instance of the server that knows nothing about the other clients.

You can use an in-memory database such as Redis to store information about connected users by identifying them by their id.
Let's suppose you are using messenger platform for your bot. Each message you receive from Facebook to your webhook comes along with some information, such as the id of user who sent the message, the id of the page to which the message was sent, etc. You can then use Redis to store any information about any user (userID, userName, etc). Once you receive a message, you just query information about the user using their id as the key.

Session is just the variable on server. you can store in variable, array or database (better option in case server restarts). so it totally depends upon your logic.
Let say for each connected user id you will have its data.
var userData = {1: {username: "bob" }, 2: {username: "alice", ... }}

You need to use cookies, sessions and a session store.
If you use a proper session library, it will create cookies automatically for the user on the client and a session on the server.
On the browser, only the sessionID is stored in the cookie, on the server, you can attach as many properties as you want to the session. To persist them, you can use Redis.
Use a combination of these three libraries (notice the first and second are actually different):
express-sessions
express-session
redis
Then your initialize it in this way ...
var redis = require('redis');
var client = redis.createClient();
var crypto = require('crypto');
var session = require('express-session');
app.use(session({
secret: 'A-SECRET-NOBODY-KNOWS',
resave: false,
saveUninitialized: true,
genid: (req) => {
return crypto.randomBytes(16).toString('hex');;
},
store: new (require('express-sessions'))({
storage: 'redis',
instance: client, // optional
collection: 'sessions' // optional
})
}));
Finally using the sessions is as simply as
//read
let token = req.session.token
//write
req.session.token= 'A very secret token';
I wrote a tutorial that has more information on how to use sessions, scroll down to part 6.

Related

Creating and maintaining a Redis session store replica in a nodejs expressjs app

Pretty much, I'm wondering how to create, and maintain a replica of a Redis session store in a Nodejs app with a microservices architecture.
Short background (somewhat)
I'm planning the architecture of a project I'm gonna start working on, and have decided to use Redis store for storing user sessions. I'm trying out a microservices architecture, and essentially, there's gonna be an authentication service that writes, and reads the to the session store as needed. What instantiating the store looks like usually:
const express = require('express');
const session = require('express-session');
const redis = require('redis');
const redisStore = require('connect-redis')(session);
const redisClient = redis.createClient();
const { SESSION_OPTIONS } = require('./configs/session');
const app = express();
app.use(session({
...SESSION_OPTIONS,
store: new redisStore({ host: 'localhost', port: 6379, client: redisClient })
}))
However, throughout my app, almost all user actions will have to be authorised, which involves checking user credentials stored in the session. Usually, in my monolithic projects, it looks something like:
router.get('/someUserAction', ensureAuthorisation, (req, res) => {
...
})
where ensureAuthorisation is a function like:
ensureAuthorisation: (req, res, next) => {
if(req.user.isAuthorised) {
return next();
}
return res.status(401).json({success: false, msg: 'fail msg'});
}
Because almost every user action will require authorisation, hence reads to the Redis sessions store, I'm assuming this wouldn't be good for the authentication service, that uses the same store but writes and reads less frequently. Thus, I want to create a separate service, the authorisation service, which uses its own Redis store. This store for the authorisation service should be a copy of the authentication service's store, removing, and updating sessions accordingly.
My initial thought was to have some form of asynchronous communication between both stores, with the authentication store writing new sessions to the authorisations... Question is, is this even possible, or would the copy store be more of a cache?
This was a bit long, and maybe confusing, so please ask any questions if you need further explanation (be kind to me pls lol). ANY HELP IS APPRECIATED 🙏
You can go ahead using a single Redis only rather than creating a cache for other service.
use redis in sentinel mode, authorization service wirtes on the master and other read from the replicas.

Is there something I can use to uniquely identify a device connecting to a socket with Socket.IO?

I'm working on an app that keeps users together in a room using socket.io. While using the app, I'm keeping track of actions the users take. If a user disconnects accidentally (in my use case, their phone rings, or the screen shuts off), I want them to be able to re-enter the room as the same 'user' without requiring a login so the actions they've tracked stay with them. I tried using socket.conn.remoteAddress, but that doesn't seem to be consistent enough to rely on.
For now, I'm requiring the user to manually enter a username and match to the user with that name on the server, but I'd rather it be automatic and invisible to the user, not to mention more reliable than what each user inputs.
Use a cookie. When they connect, check if their unique cookie already exists. If not, create it with a unique ID in it. If it does already exist, use the unique ID in it to identify the user.
From the connect event in socket.io, you can get the cookies here.
const socketCookieName = "socketUser";
const cookieParser = require('socket.io-cookie-parser');
io.use(cookieParser());
io.on('connection', function(socket) {
// all parsed cookies in socket.request.cookies
let user = socket.request.cookies[socketCookieName];
if (!user) {
// create unique userID and set it in a cookie
user = /* create some unique userID here */;
// set this into a cookie
}
// now user will be your socket.io userID
});

Caching API's data which is associated with user session using Redis

How to cache an API's response in a user session using Redis.
So that for each other user the information will be depends on the user session I'd.
You should craft the SessionId in such a way that it uses a valid set of characters for a Redis key.
Then you choose a prefix, sess for example, and you concatenate that with the session id.
const redisKey = `sess_${session_id}`
redisClient.get(redisKey, (err, reply) => {
console.log(reply)
})
Also, don't forget to set/update the TTL (time to live) to your key when you set its value so it disappears after a while without any user interaction and you avoid overflowing your Redis server capacity.

Authentication with Node/Express/Socket.IO

I have a node/socket.io/express server that's connected to a HTML file (like so). So visiting the web address connects you to the server. I am trying to set up a system where by, said server is being run on multiple computers at a time and by way of some sort of username and password authentication, visiting the webpage with specific credentials connects you to one of the computers with those same credentials running the server.
Ive seen mention of "Redis" from previous similar questions but they are pretty old and im wondering if there is a newer or better way of achieving this.
You won't find a lot of up-to-date documentation since Express 4 is kind of new, so let me try to remedy that here :
Authentication in Express 4.x and Socket.IO 1.x
Let's start with a confusion I think you're making:
What is Redis?
Redis is a data structure engine. It allows you to store key/values pairs, nothing more (In this context). The only thing it can do for you when building your authentication system is storing the data, user info, session ids, etc. In your case, you can share a store between multiple machines, the same way you'd share a database, or a text file.
Redis
Authenticate user to node/express server
One of the ways you can do that is by using passport. Passport is a middleware dedicated to authentication on Node.js. It is made for use with Express and relatively easy to setup. There is an excellent tutorial series on how to setup passport with your express application, so I won't detail this part, please take the time to go through the series, it's invaluable knowledge.
Here's the link to the first part, which is the one I'll focus on for the next step.
Add socket.io to the mix
Socket.io doesn't have access to the session cookies that you create in part 1. To remedy that, we will use the passport-socketio module.
Passport-socketio requires a local session store, as opposed to a memory store. This means we need some way to store the session data somewhere, does that ring a bell?
Exactly, Redis.
You can try other stores, like mongoDB or MySQL, but Redis is the fastest.
In this example, I'll assume that your express app and passport are already operational and will focus on adding socket.io to the app.
Setup :
var session = require('express-session'); //You should already have this line in your app
var passportSocketIo = require("passport.socketio");
var io = require("socket.io")(server);
var RedisStore = require('connect-redis')(session);
var sessionStore = new RedisStore({ // Create a session Store
host: 'localhost',
port: 6379,
});
app.use(session({
store: sessionStore, //tell express to store session info in the Redis store
secret: 'mysecret'
}));
io.use(passportSocketIo.authorize({ //configure socket.io
cookieParser: cookieParser,
secret: 'mysecret', // make sure it's the same than the one you gave to express
store: sessionStore,
success: onAuthorizeSuccess, // *optional* callback on success
fail: onAuthorizeFail, // *optional* callback on fail/error
}));
Connect-redis is a session store package that uses redis (in case the name isn't obvious).
Final step :
function onAuthorizeSuccess(data, accept){
console.log('successful connection to socket.io');
accept(); //Let the user through
}
function onAuthorizeFail(data, message, error, accept){
if(error) accept(new Error(message));
console.log('failed connection to socket.io:', message);
accept(null, false);
}
io.sockets.on('connection', function(socket) {
console.log(socket.request.user);
});
The user object found in socket.request will contain all the user info from the logged in user, you can pass it around, or do whatever you need with it from this point.
Note : This setup will be slightly different for Socket.IO < 1.x

A few questions about express.cookieSession()

On my node.js server (running express and socket.io), when a person connects, I add them to an array and save their location in the array into their session so each connection has access to their own information like so:
session.person_id = people.length;
session.save();
people.push(new Person());
//people[session.person_id] => Person
And the only thing that I save in the session is person_id. I have been using express.session() to handle this, which has been working fine until I started sending information to everyone who is connected at once. As I loop through their connections and get their sessions, sometimes (I can't figure out how to dupe the error) session exists but not session.person_id.
Anyways I'm hoping that by changing how I store the session, it can help me figure out my problem. So I have a few questions that I can't find answers to anywhere.
Where is the cookie from express.cookieSession() stored? Server-side or client-side?
Does express.cookieSession() allow for multiple servers running behind a load-balancer?
Is it possible for a user to manipulate session data when using express.cookieSession()?
1 - Where is the cookie from express.cookieSession() stored? Server-side or client-side?
The cookie is sent on the replies from the server, and the browser sends that cookie back with each request.
2 - Does express.cookieSession() allow for multiple servers running behind a load-balancer?
Yes, if you use a shared store (like RedisStore)
3 - Is it possible for a user to manipulate session data when using express.cookieSession()?
Not if you use signed cookies (the default for session cookies in express when you provide a secret when initializing the session.
var redis = require('redis').createClient();
app.use(express.session({
secret: "some random string",
store: new RedisStore({client: redis})
}));

Resources