Setting cookies in Express.js XHR response object from node_redis callback - node.js

I'm having trouble getting a cookie to be passed back with an Express.js response. The situation is that I'm making an XHR containing user credentials, checking if the user exists, if the user does exist and credentials are correct I store the users info in Redis via node_redis. In the callback that I hand Redis I create a cookie on the response object, then send the customer object to the browser.
Here is the Redis code (note: I'm not using Express sessions so I just added the Redis client into req.session via middleware):
req.session.hmset('user', customer, function(err, status) {
res.cookie('sid', 'sessionID', {
maxAge: 3600000,
path: '/',
httpOnly: false
});
res.send(customer);
});
The odd thing is that when I add a console.log(res._headers); right after creating the cookie, there is a 'set-cookie' header present, but it never makes it to the browser. Might I have bad request headers? Something else? I'm completely stumped here...

Related

How to get HTTP-only cookie in React?

I'm currently developing a MERN stack application and the authentication I use is JWT and saving it in my cookie. This is how I send the cookie after the user login.
res
.cookie("token", token, {
httpOnly: true,
secure: true,
sameSite: "none",
})
.send();
And I am logging in the user by getting the "token" cookie in my backend. However, I implemented Redux with this application and every time I refresh the page, it automatically logs out. What I want is to detect in my front-end(React) the "token" cookie in my browser and I can't get it. I've tried using npm js-cookie and still can't get it. Is there a way to get the "token" cookie? Or use redux-persist based on what I've read? Please help, thanks.
Like already explained by an other answer, you can't access httpOnly cookies via JS.
I personally would recommend you to use a diffrent approach. Sure, cookies and httpOnly sounds like a good Idea, and you may think that cookies are a thousand times better than localStorage, but at the end, it doesn't really matter if you store the token in localStorage or in a cookie. You could argue about cookies vs localStorage for hours, but both have their vulnerabilities (e.g.: cookies: CSRF-Attacks (https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html), localStorage: XSS).
Now, while you could theoretically use localStorage here, I am not advocating using it. I would recommand you to just ditch both cookies and localStorage and store the JWT in your app-state (be it with the context-api, redux etc.) and send the JWT with an authentication header with all the request you make from the front to backend. Of course your backend would then need to verify that token. You could, for example, just implement an authentication middleware that you add to all the routes that need authentication. Expiration is also really easy because you don't have to sync the expiration of the JWT and the cookie anymore. Just set the expiration on the JWT and the verification of that token in the auth middleware will catch that. If you want to know why this method is safe against CSRF-attacks, look here: Where to store JWT in browser? How to protect against CSRF?
Here are some good articles, I would really recommand you read a bit of the first one:
https://hasura.io/blog/best-practices-of-using-jwt-with-graphql/
https://medium.com/#ryanchenkie_40935/react-authentication-how-to-store-jwt-in-a-cookie-346519310e81
Although you cannot do anything with the httpOnly cookie in the frontend, there definitely IS a way to handle frontend-sent httpOnly cookies and extract your JWT from that cookie, all in the backend of your MERN stack app.
As far as persisting the user and preventing the 'logout upon refresh' issue, you will have to create a useEffect hook that constantly checks whether the token exists or not - we'll get to that later.
First, I recommend using Cors in your backend:
const cors = require("cors");
app.use(
cors({
origin: ["http://...firstOrigin...", ...],
credentials: true,
})
);
Once that's ready to go, set the following options when creating your httpOnly cookie. Also, create a non-httpOnly cookie that tracks your httpOnly cookie with same expiration date and a boolean value instead if the JWT. This will allow you to use the 'universal-cookie' library and actually read the non-httpOnly cookie in the frontend:
res
.cookie("token", token, {
origin: "http://...firstOrigin..."
expires: // set desired expiration here
httpOnly: true,
secure: true,
sameSite: "none",
})
.cookie("checkToken", true, {
origin: "http://...firstOrigin..."
expires: // same as above
secure: true,
sameSite: "none",
})
Having created a 'checkToken' cookie that mimics our actual 'token', we can use it to set the state (useState hook) and persist the user if it exists and not expired, through the useEffect hook.
However, to send it correctly, we must specify a few things first. In this example, I will use axios to make such API call in the frontend.
Note that every API call's request header will contain our httpOnly cookie and it's content - we can confirm this by opening chrome dev tools' network tab, make the API call, then check the "Request Headers" for the "Cookie"...
const cookies = new Cookies();
const checkToken = cookies.get("checkToken");
const AuthUser = () => {
const [user, setUser] = useState(checkToken);
useEffect(() => {
async function checkToken() {
await axios
.post("http://...yourBackend.../authToken", {
withCredentials: true, // IMPORTANT!!!
})
.then((res) => {
// handle response - if successful, set the state...
// to persist the user
)}
.catch((err) => {
// handle error
)}
};
checkToken();
}, []);
// Implement your login behavior here
}
Once that's done, we can confirm that we're getting such token in the request body of our API call in the backend (wherever that's handled), log the cookie in the console to check for it, then store the cookie's value in a variable to enable verification of said cookie:
app.post(".../authToken", (req, res) => {
// Get all cookies from request headers
const { cookie } = req.headers;
// Check to see if we got our cookies
console.log(cookie);
// Handle this as you please
if (cookie == undefined) return;
const token = cookie.split("token=")[1].split(";")[0]; // Yep, it's a string
console.log(token); // Check to see if we stored our cookie's JWT
// Some middleware:
jwt.verify(token, process.env.TOKEN, (err, user) => {
// if success upon verification,
// issue new 'token' and 'checkToken'
});
});
Done.
Please note that this a general implementation and serves as only a guide to understanding the functionality of httpOnly cookies. OP never provided original code to go off of.
I hope this helps. Godspeed.
You can't. "httpOnly" means "JavaScript cannot access it".
Using Redux-Persist would also not really help you determine if you are still logged in or if your session is timed out. That data could have been persisted weeks ago or the token could have been revoked.
The most sensible thing you can do it set up a /whoami endpoint on the server and just as a first action while your application initializes sending a request there. Either info about your user comes back -> great, save it and display it. Otherwise you get a "401 unauthorized" which means the user is not logged in and needs to log in.
We run into similar problem when improving the security of auth workflow on a project Created using React/Django
The question was: What is the best place to store JWT ?
After research we ended up implementing Oauth2 protocol, here is an article that helps you understand the logic if Refresh token rotation
Our implementation was
Generate 2 tokens on backend side (Access Token [short life] and Refresh Token [long lifespan])
Refresh token should be stored in HttpOnly cookie (as they mentioned in responses, it is not accessible on Client side by JS)
At frontend level we use only Access Token, and when it is expired, we make a call to backend to regenerate another Access and Refresh
Backend will access the Refresh Token in HttpOnly cookie and decide if it is valid to generate new Tokens
If Backend generates new valid tokens, it sends Access Token to frontend and update Refresh Token in the Cookie
Ps:
by this logic, you have no access to refresh token on frontend side, so when your Access Token no longer valid you tell the server to check Refresh Token stored in HttpOnly Cookie if it is still valid then regenerate other valid Tokens
I hope this inspires you

Can fs.writeFile called in a file routed to express, middlewared by session, delete the session cookie?

I routed a js file to the main js file using express and it's router middleware. Also each path is middlewared by session. After authentication, a get request which is received only when req.session.loggedIn exists, will execute on every request since the login.
But on a put request, it will execute once and then will be blocked by the authentication validator which is a middleware in both requests.
When commenting out the fs.writeFile block the cookie remains as expected, and put request are executed more than once.
Can fs.writeFile mess up cookies?
This is the problematic block:
fs.writeFile('./data/tasks.json', stringed, (err)=>{
if(err){return console.log(err)}
console.log('req.session from tasks put:',req.session)
}, ()=>res.send(`Successfuly added task. `))
This is the validation middleware:
const validator = (req, res, next)=>{
console.log('req.session in validator:',req.session)
if(!req.session.loggedIn){
return res.status(401).send('Please log in.')
}
next()}
This is the session middleware:
app.use(session({
secret: ';klmkljhjkhn;jk',
cookie: {
maxAge: 6000065456468476813541684864764561231,
httpOnly: true,
secure: false,
},
saveUninitialized: true,
resave: true,
}))
This is the routing middleware:
app.use('/tasks', validator, require('./routes/tasks'))
The problem that you describe is not easily exhibited by the code in your repository that you linked to in the comments. I was able to login and add 2 tasks successfully via POST, and then read them back, without losing the session data.
It is possible that you were having some timing-related issue with the 2 callbacks: fs.writeFile() takes 1 callback, so the other callback was never being executed; I was unable to verify this, since the code in git does contain this exact statement. Since express-session saves the session only when the response is sent, and it didn't happen here, it was likely timing out - so it's probable that:
You started a request to POST /tasks - it loaded the empty session (not logged in)
You then logged in, and that response finished
Finally, the first request to POST /tasks ended - and express-session resaved the (empty) session, so it overwrote your logged-in session
This can be avoided if you disable resave: true.
To sum up: fs.writeFile is not causing session problems by itself, but can lead to requests which overwrite session state, especially in the presence of:
callback bugs
very long writes
resave: true
The solution to my problem was disabling nodemon, which kills the server on each file change, which is what fs.writeFile just does. I'm so relieved!

passport-local cookie and session database handling

I am currently trying to setup an oauth service for my current project. Being new to all this, I have read many articles on the subject, but I am still missing some pieces.
My environment is:
servers: node, express, passport
authentication: local strategy (using my own accounts / passwords)
I have a database with user/password ( passwords run through bcrypt)
web interface: react, server accesses through superagent
On my nodejs server, I am using these modules:
express
express-session
express-mysql-session
passport
passport-local
bcrypt
Different parts of the solutions are working: I can create new users, create new sessions, see their content in the express-mysql-session database.
However I am quite confused on the following:
when my web client tries to access protected routes, I don't seem to be getting any cookie in the request. Is it that superagent will not send my passport cookie by default? I read somewhere that in single page apps, jwt might be more appropriate, is that linked to this problem?
despite all I read, I am still confused about deserializeUser. My understanding is that with the passport-local solution, upon access, the web client will send the session cookie, which contains the session Id. Passport will fetch further information concerning this session from database, and then continue to handle the request. Does this session info retrieval happen automatically (in express-mysql-session?)? Or do you have to "manually" do it in deserializeUser (many examples show a User.findById call in there)? If you have to do it "manually", it means that you have to access the express-mysql-session db using another connection than the one this module is using?
to log out, is req.logout() enough to ensure the session is erased from the session db entirely?
Answers I found so far:
One has to add the withCredential method to superagent, to get it to send authentication cookies:
res = await superagent
.get(url)
.withCredentials()
.send();
On the CORS side of things, on the server, the 'credentials' option is required if using the 'cors' npm module, for instance:
app.use(cors({
origin: ['http://localhost:3003'],
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
credentials: true,
}));
All session information is automatically retrieved by these modules. However, many example show this call going back to the user database to get more information (rights, other info). The goal is to avoid having the same information in two locations (sessions db, and user profiles db), and having these getting out of sync (when an account gets closed etc...)
req.logout() disconnects the session, but the session information sticks around in the database.
The following question put me on the right track: how to delete cookie on logout in express + passport js?. You need to use req.logout, res.session.destroy, and while you're at it res.clearCookie to delete the client cookie:
router.post('/logout/',
(req, res) => {
req.logout();
res.status(200).clearCookie('connect.sid', {
path: '/',
secure: false,
httpOnly: false,
domain: 'place.your.domain.name.here.com',
sameSite: true,
}).end();
req.session.destroy();
},
Session is disconnected, database cleaned, cookie gone.

Can't get redis session in sailsjs

I have two APIs. When i hit first API from Atom, i am setting a parameter in session which is successful. But when i hit second API and trying to get session by req.session, it creates a new session instead of giving previous session. Ho do get session and parameter i set in first API into second API.
Below is my first API code in which i am setting session parameter.
function firstAPI(req, res) {////This is POST API
session = req.session;
////Doing something with req
session.message="my message";
sails.log.info("session " + JSON.stringify(session));
res.send(""session is set);
}
In terminal i am getting following session
session {"cookie":{"originalMaxAge":180000,"expires":"2017-02-28T05:03:25.304Z","httpOnly":true,"path":"/"},"message":"my message"}
Below is my second API code in which i am trying to retrieve session.
function secondAPI(req, res) {
sails.log.info("session= " + JSON.stringify(req.session));
}
Log for second API is
session= {"cookie":{"originalMaxAge":180000,"expires":"2017-02-28T05:04:18.623Z","httpOnly":true,"path":"/"}}
Following is my config/session.js file
module.exports.session = {
secret: '123abc',
cookie: {
maxAge: 3 * 60 * 1000,
},
adapter: 'redis',
host: 'localhost',
port: 6379,
db: 0,
prefix: 'sess:',
}
You can see i have set cookie time 3 mins. and in between i hit the second API. Anyone find any mistake in this?
Second request must send the cookie set by first request (in Cookie header) in order to retrieve the session.
Browsers as a client sends cookie set for the domain, by default. Other clients like cURL need to be specified. If you specifically want to use Atom check whether/how it supports to send cookie.
See Cookies & sessions:
Sessions are server-side files that contain user information, while Cookies are client-side files that contain user information. Sessions have a unique identifier that maps them to specific users. This identifier can be passed in the URL or saved into a session cookie.
Most modern sites use the second approach, saving the identifier in a Cookie instead of passing it in a URL (which poses a security risk). You are probably using this approach without knowing it

Create session only after successful authentication in express

I have a requirement to create a session only after successful authentication.
I was able to create redisStore based session using express middleware, but it creates session when the first request comes to server.
But how I can create session only after successful authentication.
I googled somewhat, and foundreq.session.regenerate() (but I found the issue as below mentioned in this thread:
Regenerate session IDs with Nodejs Connect)
But in regenerate case also, it creates a fresh one, assuming old one is created already, and is created with same parameter.
So there is any other way to create a new session ID only after successful authentication..?
You may be conflating the idea of a session with the idea of an authenticated session.
It's normal for all users to have a session - even the anonymous, not-yet-logged-in users. The difference between this and an authenticated session is just that, locally on your web server, you specify that a particular user has been authenticated.
For example, once you authenticate someone, you can set:
req.session.isAuthenticated = true;
Then, when rendering pages, your controllers can do something like
function(req, res, next) {
if (!req.session.isAuthenticated) return res.redirect('/login');
res.render('appPage');
}
This might not be the exact answer you're looking for, but I'll answer the title for future readers:
From experimenting with my application, I've noticed that express-session sets the session cookie only if you manipulate the session object.
For example consider the below code:
app.post('/login', function (req, res) {
var authenticated = false;
if (req.session.authenticated) {
// session cookie is already set
authenticated = true;
} else if (/*validate the user here*/) {
console.log(' authenticating');
authenticated = true;
// if below line executes, the response will have Set-Cookie header
req.session.authenticated = authenticated;
}
res.json({
status: authenticated
//if --^ false, no session cookie will be set since we didn't manipulate session object
});
});
Even though a request creates a session object in memory for us to use, the Set-Cookie header seems to be sent only if we manipulate (or tamper with?) the session object created.
Unless we sent the Set-Cookie header along with the response and session id is stored in cookie at client side, I wouldn't consider it as an established session and worry about it.
Hope it helps.
Side note: This is the case of a cross-domain ajax request, might be different for normal http request, perhaps someone can confirm this

Resources