share user sessions between Node.js and Clojure - node.js

I am using Clojure / Friend, and I would like to hijack a session from a neighbouring node.js server :)
What I mean is that there is a node.js + Express 3 server that stores sessions into a redis database, using connect-redis.
I have access to the node.js source code, and therefore to the secret passed to the express session used to generate the sid. I modified the connection section to look like this (the built-in in-memory store was used previously) :
var session = require('express-session')
, RedisStore = require('connect-redis')(session)
, redis = require('redis')
, client = redis.createClient();
...
app.use(session({
secret: "super-secret-key",
store: new RedisStore({client : client,
ttl : 60*60*24,
prefix : 'sess:'})
}));
I can see the sessions stored in Redis :
// using redis-cli
KEYS *
1) "sess:yNV3MQOOTZSEgzl0RH2lyWVW"
The login page is situated on the node.js side of the app. But the routes of the API are shared (via nginx) between Node.js and Clojure, so I receive the cookies in the incoming requests in my Compojure / Friend app.
I am struggling to get the Friend custom-credential function right :
(defn custom-credential [{:keys [username password] :as trial}]
(let [cookie (...)
redis-session (...)
is-signature-valid (...)]
;; I can get all the above bindings just fine
;; ie. I extract the cookie from the request
;; I read the cookie ID and fetch the cookie content from Redis
;; I extract the signature of the cookie and check it (I replicated [node-cookie-signature][8])
(when is-signature-valid
{:identity "cookie-id"})))
(defn secured-routes [unsecured-routes]
(friend/authenticate
unsecured-routes
{:allow-anon? true
:credential-fn custom-credential
:workflows [(workflows/http-basic)]}))
I have several problems :
I am redirected to the login page even when my authentication function returns an {:identity ..} map
But I cannot figure out how to plug it into Friend so that the cookies work as well. Using the default memory-store then the cookie is replaced by a default ring-cookie (obviously not what I want). I tried writing a custom cookie-store using Redis as a backend, but I need to write JSON to keep the node compatibility, and I loose the ::user information.
Note : I am quite flexible at this point so if I should use something else than Friend or Compojure I would also accept that.
Sorry for the long post, I really hope someone can help.

How do I get the cookie contents on my validating function ?
The ring request will contain any cookies that the client sent, however it may not send those cookies if your Clojure server is on a different domain.
how do I validate the user from the content of the cookie ? Ie. how can I make sure the cookie has not been altered (ie. replicate what the connect-redis is doing with the secret) ?
Best practice is for the cookie to be a random number, and to store session details in redis. If that's how your Node server works then you can just lookup the details in Redis. Alternatively if the client provided an encrypted session cookie, then you will need to decrypt it in the same manner as Node does.
Alternatively, you might want to look at JWT auth tokens, which are designed to be shared and authenticated by many different servers.

Related

Express session, how do I get the session after posting? I see the session ID in browser under cookies, but it's undefined in request object?

Sorry guys, I'm really new to sessions and cookies and I'm trying to understand the mechanism behind it. I wanted to add register/login to my simple website and in order to do I need to understand web authentication and I really think I will have tons of questions regarding this topic.
Initially, I have register page that sends info after clicking submit button to a node server using express.
I'm trying to see what happens, so I've created a session in post route, it's created in the browser (connect.sid), then I commented out the part that creates that session and just tries to redisplay the session object, but it's undefined, but I still can see the session in the browser's cookies section, so what's going on? Thanks
app.use(session({
secret:"my test secret",
cookie:{},
resave:false,
saveUninitialized:false
}))
app.post("/register", (req, res) => {
req.session.usertest = "testsession_hardcodedvaluefornow";
console.log(req.session.usertest); // -> this is okay when above line to create is uncommented
//but when I comment the session assignment, it becomes undefined?
res.send("In register...");
})
I can see the session cookie even after commenting out the create session and posting over and over.
connect.sid s%3A_TahsTv0xhY-iHIdjDRblYJ_aZZ5oiSd.do7JcOGR1FaXPcFFIQ6hg5AW%2B0XVsYwIRO8vndyjDzs
req.session.id produces a different value (not undefined) even if I delete my session in the browser, so not sure where that comes from.
There is no "usertest" key in the session object, therefore it is undefined. The reason it's not undefined when you uncomment that line is because you create that key yourself in that instant with that line.
You can get the whole session object by using req.session, the session id by using req.session.id and the session cookie by using req.session.cookie.
Edit
To further clarify: a session will be made for every connected client. That is why you can see the cookie in the browser. That has no meaning however, it's just a way to uniquely identify that client (without actually telling you who that client is). Any information about that session (whether they're logged in, user id,...) needs to be stored in a session store. Express-session will use memory store by default, if the server restarts all that information will be lost, which is why that key doesn't exist. In order to preserve that information it has to be stored in a persistent session store. More information on session store implementations for express-session and how to use them can be found here: https://www.npmjs.com/package/express-session
As for the cookie values you get, those are the default ones set by express-session since you haven't set any yourself. You can change the maxAge to let it expire (or set it so 10 years or more for a more persistent session), you can specify a domain to which that cookie belongs, you can set it to secure (to only allow it over secure connections, e.g. https) and httpOpnly is by default true. httpOnly cookies cannot be accessed/altered by the client (although you can see them in the browser), do not set this to false.

Why use cookie-session in addition to passport.js?

My understanding of passport.js so far is that passport.js serializes the user object and sends an ID every time to the client. I am just starting with it so sorry if it's a silly question:
Instead of express-session, I am using cookie-session as I am a beginner. My understanding of cookie-session is that it sends a session ID every time, and this ID can be used to look up the database when needed.
Now, I don't understand why we can't just use the passport.js ID? Why do we need to use cookie-session in addition? Also, (this may be a little unrelated, but) is the difference between session-based authentication and token-based authentication that this ID that's shared is dynamic, or changing every time? Is this still the standard and modern way of doing it in 2020?
"Instead of express-session, I am using cookie-session as I am a beginner."
using cookie session does not make anyone beginner. If you are going to store large data, use express-session, cause it stores the data in the database or redis, keeps the database id of that data, so when it gets a request, fetch the database with that id and compare the request credentials. On the other hand, cookie-session stores the data upto 4kb in the cookie on the user browser and since only user-id is stored in the cookie with passport.js, generally cookie session is used.
passport.serializeUser(
(user, done ) => {
done(null, user.id); // stores the id<4kb
}
);
When client authorizes your app, google send the responds to your callback url.
app.get("/auth/google/callback", passport.authenticate("google"))
passport.authenticate() will call req.login() this is where passport.user gets generated. req.login() will initiate the serializeUser() which determines which data of the user should be stored in the session.
passport:{user:userId}
Now this object will be assigned to req.session. so we will have req.session.passport.user
Everytime when you make a request to a server, browser automatically checks if there is cookie set related to that server and if there is it automatically attaches the cookie to the request. If you were using token based authentication, you had to manually attach the cookie to request everytime you make a request. Cookie is just transportation medium, you store data and move the data, you can even store the token in cookie. Cookie is not just related to authentication. If you have server-side project, you have to set cookie.(this is just a side node).
"My understanding of cookie-session is that it sends a session ID every time, and this ID can be used to look up the database when needed."
so far I explained how session is created. Now what happens when user makes a request?. In app.js file you should have two middleares.
app.use(passport.initialize());
app.use(passport.session());
app.use(passport.initialize()) this function checks if req.session.passport.user exists, if it does it will call passport.session(). if it finds a serialized user object in the session, it will consider this req is authenticated. And then deserializeUser() will be invoked. it will retrieve the user and attach it to req.user
You don't need to use session. It is totally upto you. Just put {session: false} in route. You don't need to write passport.serializeUser and passport.deserializeUser.
cookie-session puts cookie on client system, and it is sent each time with request. passportjs search that cookie and run deserializeUser to convert it into object and attach it with request object.
express-session stores session data on the server; it only saves the session identifier in the cookie, not session data.
where as cookie-session is basically used for lightweight session applications. it allows you to store the session data in a cookie but within the client [browser]. Only use it when session data is relatively small and easily encoded as primitive values See this question for more understanding
const express = require('express');
const { Router } = express;
const router = new Router();
router
.get('/', passport.authenticate('google', { session: false }))

What is the additional information in connect.sid cookie string apart from the session id?

I am using Node JS with express-session.
One question answered here advises to use req.cookies['connect.sid'] to get the session ID. Another answer suggests I use req.sessionID
When I compare the two the req.cookies['connect.sid'] has a string like the following:
s:G1wOJoUAhhemRQqCs7dAGlMIk5ZGaJUg.z1/HrHTfndRqKpXza8qWuwHLS067xrWfVgqTDDGYoos
req.sessionID has a string like the following:
G1wOJoUAhhemRQqCs7dAGlMIk5ZGaJUg
If the second string is the session ID (G1wOJoUAhhemRQqCs7dAGlMIk5ZGaJUg), what is the other information in the connect.sid cookie?
Tried looking for the answer via google and other websites with no luck.
Thanks,
Darren
express-session stores all session information server-side. If you use an sql database, you'd have a table for your sessions, that would look like this :
sid | sess | expire
R02GJn2GoyaqRyten1BEGbHa0XCbj529 | {"cookie": "originalMaxAge":null,"expires":null,"httpOnly":true,"path":"/"},"mybool":true,"userid":16}
That's the answer to your question, and a short explanation of what the data means, sessionID is just a (primary) key to access the data that is only available server-side.
Now from your question it looks like you're planning on using express-session wrong.
To use express-session on your express server you would include it like so :
const session = require('express-session');
app.use(session({
name : 'mycookie',
secret : 'mysecret',
saveUninitialized : false,
resave : true
}));
and every request that goes into all subsequent routes after this will have a .session attribute. In any case, you should never need to access the session id yourself, the middleware itself authenticates every request against whatever store you used for express-session.
app.get('/givemeasession',function(req,res,next){
req.session.mybool = true;
req.session.somedata = 'mystring';
req.session.evenobjects = { data : 'somedata' };
res.send('Session set!');
});
app.get('/mysession',function(req,res,next){
res.send('My string is '+req.session.somedata+' and my object is '+JSON.stringify(req.session.evenobjects));
});
Bottomline : you shouldn't ever need to access the cookie yourself, or do anything with it, because any cookie auth is automatically handled by the middleware.
The accepted answer is correct by saying, don't handle cookies and sessions by yourself, but I don't think it answers the question:
The second part in connect.sid is the cookie signature.
In case of express-session this is generated by the package cookie-signature with the secret you set when you set up the session middleware.
As you can see in the source-code, the very first step is verifying if the cookie was signed by the secret. Afterwards the cookie is retrieved from the store.
(In case of the memory store, the retrieval also deletes the cookie if expired)
Source: https://github.com/expressjs/session/blob/1010fadc2f071ddf2add94235d72224cf65159c6/index.js#L542

One database per user security

I develop an app with Ionic where each user got its own PouchDB database synchronise with its own remote CouchDB database. I use couch_peruser=true so any user that want to access its database need to authenticate.
This system is easy to do if we store the username & password locally or if we ask the user to give them anytime a sync is needed but none of these options are good (security concern or non user-friendly).
I came with those two options, but none is working:
1) The best option that came to me was to use Cookie Authentication, just save the token locally and use it, but unfortunately to connect with the token couchdb ask to use the header :
Cookie: AuthSession={TOKEN}
But this is not possible because it is a non-authorized header (unsafe) and is rejected by the browser.
2) Second option is to use couchdb Proxy Authentication but it is the same as saving the username and password as the token is valid forever.
Is there any way other way of handling authentication ? I was thinking to use an alternative users database, generate a fake password & username then send this credential to be saved into the user app. In this case if the security is compromise the user can change its password so the server can change the second password too (in the same way we revoke a token), but then there is still a problem because with the stolen credential it is always possible to access directly to the couchdb database without being seen..
Thanks for your help, i was not able to use couchdb-auth-proxy so I ended up with the following solution that has the advantage to prevent direct access to couchdb :
1) Create a node server to authenticate the user, if auth successful then return couchdb token to the app for cookie authentication
2) Create a node server used only as a couchdb proxy using node-http-proxy
with the following code :
(it is required that this router code come very early in the express middleware otherwise it might change the response and pouchdb sync does not work, so place it before app.use(bodyParser.json())
)
router.all('/*', (req: Request, res: Response, next: NextFunction) {
let token = req.get('X-Auth-Cdb-Token');
let httpProxy = require('http-proxy');
const proxy = httpProxy.createProxyServer({
target: target,
});
req.headers['Cookie'] = 'AuthSession='+token
proxy.web(req, res);
});
3) In your app set the pouchdb remote database with the following header :
remoteDB = new PouchDB(url, {
skip_setup: true,
ajax: {
headers: {
'X-Auth-Cdb-Token': couchdbToken
},
withCredentials: false
}
})
If you write a Progressive Web App, the Cookie Authentication is great for this, because the browser handles it for you. Use the pouchdb-authentication to log in directly to CouchDB.
On the CouchDB side, configure the Cookies as persistent, and put some longer lifetime on it. You can set it to 2 weeks, for example, so your users will only be asked for the password if they haven't logged in for two weeks.
The cookie TTL is automatically refreshed once a certain threshold is reached (I recall it's at half of the cookie TTL, so it would be refreshed if the cookie is more than a week old).
CouchDB is built for the web, so you can take advantage of it. ;-)

Cookies handling when client-side is also an ExpressJS app

As far as I'm concerned, for a server side application to know what clients are communicating with it, it will save a cookie in the client with the session ID.
That is indeed what express-session a popular package for session storage in ExpressJS says in the documentation
Note Session data is not saved in the cookie itself, just the session ID. Session data is stored server-side.
So I believe I can assume this is strategy used by Express to maintain user data in sessions as well.
I did something else: I'm using Redis to store the Session data in my ExpressJS server app.
So having said that, my problem is that my client application is also an Express app. I have a nodejs app with Express both for client and server. There is a SPA involved in this problem, but it communicates with the 'express client' so it appears to be a different problem.
So everytime I send a request from Express Client to Express Server, there is not cookie being passed, so it can't identify a session ID and creates a new one, generating lots of garbage in Redis and making the session solution useless.
How should I save or fake a cookie in the Express Client app, or, even better, how this kind of problem is solved?
Actually if I knew what the Express Server is expecting in the request (a cookie, a header or whatever) I think I can solve the problem.
Anyone know what to do?
#Solved
Alright, so, in my nodejs client application I did the following:
login(req,res,next){
var options = {
url : 'http://localhost:8090/user/login_handler';
};
request(options, function(error,response,body) {
var cookie_string = response['headers']['set-cookie'][0].split(';')[0];
req.session.cookie_string = cookie_string;
});
}
check(req,res,next){
var options = {
url : 'http://localhost:8090/user/check_handler',
headers: {
'cookie':req.session.cookie_string
}
};
request(options, function(error,response,body){
res.json( body);
});
}
In short, when the session is created in the server side, it will respond with headers to tell the client to create a cookie. I save the important information to pass as a cookie in a different moment. The server-side then read the headers in the middleware and load the correect data to the session.
Without knowing the details of your architecture I would guess that what you want is to either set the saveUnitialized option to false, and not save the extraneous sessions, or only apply the express-session middleware to certain routes in your "Express Server" application

Resources