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.
Related
I am in the process of building a small test slack app and not clear on the architecture that is needed for authentication. This will be a NodeJS application that lives on Heroku.
When a user uses a /slash command, it is going to invoke logic that will query an external CRM system and return data. In order to authenticate into this external system, it would need to send the user through an OAUTH flow so that the access to the data is governed by the invoking users' permissions.
My confusion is to how to handle or persist these auth tokens/refresh tokens that we get back from the user authenticating during this process.
Example Steps:
Runs /user bob#gmail.com
Checks to see if user has authorized on this external system
If not, take the user through the external systems oauth flow
After authentication, we have the token that can be used to make callouts to the external systems API as that user.
Makes callout and returns data
How would I persist or check for the slack users auth/refresh token when they run the command to see if we already have it? If the token already existed, I wouldn't need to send them through the OAUTH flow again.
My Thoughts on the approach:
It almost seems like there needs to be a data store of some type that contains the slack user ID, auth token, and refresh token. When the user invokes the command, we check to see if that user is in the table and if so, we use their token to make the API call.
If they don't exist, we then send them through the OAUTH flow so we can store them.
Final Thoughts:
In terms of security, is having a table of tokens the correct way to do this? It almost seems like it's the equivalent of storing a plain text password if someone were to get that token.
Is there a better way to handle this or is this a common approach?
Your approach is right and the docs in slack API points out to this article that describe your use case where the third party is the Salesforce CRM.
In terms of security, is having a table of tokens the correct way to do this? ...
Yes, an attacher may steal your db data.
To avoid that you can store the tokens as encripted string.
In this way, a malicious user should:
steal your data from the db
steal your source code to understand what type of algorithm you are using to encript the tokens and the logic behind it
The approach is to spread all the info to get the clear token across system assuming
one or more systems can be compromised, not everyone!
Is there a better way to handle this or is this a common approach?
Usually the AES-256 is used, in detail aes-256-gcm or aes-256-cbc. There are some thread off
in performance and use cases you must deal with in order to prefer one or another.
Node.js supports both and an example logic could be:
const crypto = require('crypto')
const algorithm = 'aes-256-gcm'
const authTagByteLen = 16
const ivByteLen = 64
const keyByteLen = 32
const saltByteLen = 32
const oauthToken = 'messagetext'
const slackUserId = 'useThisAsPassword'
const salt = crypto.randomBytes(saltByteLen)
const key = crypto.scryptSync(
Buffer.from(slackUserId, 'base64').toString('base64'),
Buffer.from(salt, 'base64').toString('base64'),
keyByteLen)
const iv = crypto.randomBytes(ivByteLen)
const cipher = crypto.createCipheriv(algorithm, key, iv, { authTagLength: authTagByteLen })
let encryptedMessage = cipher.update(oauthToken)
encryptedMessage = Buffer.concat([encryptedMessage, cipher.final()])
const storeInDb = Buffer.concat([iv, encryptedMessage, cipher.getAuthTag()]).toString('base64')
/***
*
*/
const storeInDbBuffer = Buffer.from(storeInDb, 'base64')
const authTag = storeInDbBuffer.slice(-authTagByteLen)
const iv2 = storeInDbBuffer.slice(0, ivByteLen)
const toDencryptMessage = storeInDbBuffer.slice(ivByteLen, -authTagByteLen)
const decipher = crypto.createDecipheriv(algorithm, key, iv2, { authTagLength: authTagByteLen })
decipher.setAuthTag(authTag)
const messagetext = decipher.update(toDencryptMessage)
decipher.final()
const clearText = messagetext.toString()
console.log({
oauthToken,
storeInDb,
clearText
})
Notice:
the SALT logic will generate a new "storeInDb" string at every run without compromising the future reads
you may use the slack-user-id as password, so the attacher should know this information too
you must store the salt too or write an algorithm to generate it form the user id for example
the salt may be stored in a (redis) cache or other Services like S3, so the attaccher should break this other system to parse the tokens!
GCM example extrated by my module
You may find a CBC example here
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
});
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.
Can you please tell how to solve the problem of preserving the identity socket.io, for all open tabs in the browser.
Example:
I have a page open (/index, for example), I set it to connect to socket.io and get a unique session ID. But when I open the same page on another tab - ID creates another, that is, there are already two compounds with two different ID. That is right, it is logical. But, I need to send messages to these ID, simultaneously in all the tabs (including binding to user_id), and because the session ID are different - it is impossible.
If you don't want to (or can't) use some kind of session store, and want to send messages to multiple sockets with the same user_id, you can store a map with user_id as key and an array of socket as value
var socketMap = {};
And store a reference to the socket with your handshake data
io.sockets.on('connection', function (socket) {
var userId = socket.handshake.userId;
if(!socketMap[userId]) socketMap[userId] = [];
socketMap[userId].push(socket);
});
Or with some normal event data
socket.on('auth', function(data) {
var userId = data.userId;
var authToken = data.authToken;
... // verify the data...
if(!socketMap[userId]) socketMap[userId] = [];
socketMap[userId].push(socket);
});
When you want a list of socket to send message to
var sockets = socketMap['some_user_id'];
ID of the socket will be different in any cases, and should be not used to identify user in any case. It is only used to identify Socket it self.
What you are looking for is session. Ability to relate socket connection to session - is what you are looking for.
In order to do so, you need to have sessions created for new http connections as well as restore this session during handshake process of socket.io.
That way you will be able to identify that specific socket belongs to specific session.
And then user data and other stuff you keep to session. This helps in many cases - restore session on page refresh or have single session in multiple tabs as you need it.
Read this answer of how to restore socket.io session id from handshake process: http://www.danielbaulig.de/socket-ioexpress/
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})
}));