How to resolve issue with handling csrf with multiple tabs in express/nodejs? - node.js

I built CSRF protection in my nodejs/express application with the following config:
var app = express(),
cookieParser = require('cookie-parser'),
session = require('express-session'),
csrf = require('csurf');
app.use(cookieParser());
app.use(session({
, saveUninitialized: true
, resave: true
, store: new MongoStore()
}));
app.use(flash());
And with the following login form:
<form action="/process" method="POST">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<button type="submit">Submit</button>
</form>
The problem arives when user opens two browser tabs and end of the story is getting EBADCSRFTOKEN error at this line:
Let's see the following case:
User opens the form above in two separate tabs.
In first tab he do logout and signin again.
Then switches to second tab, click submit and get EBADCSRFTOKEN error.
I need to point that I destroy my session in logout route:
app.route('/auth/signout')
.get(function (req, res, next) {
return req.session.destroy(function (err) {
if (err) return next(err);
return res.redirect('/');
});
});
Because that fact that I destroy the session I destroy the secret also key that stored there. So this destroing leads to invalid token on second tab and EBADCSRFTOKEN error.
I need to resolve this case somehow. What you do in this case? Show popup to reload the page or reload page automatically?

The csrf token should be set and retrieved from cookie before form submission. Suppose, you open tabA with csrf C1. Once you open tab2, the csrf changes to C2. But, if this is set in the cookies, fetching csrf from cookies in tabA will give C2 as csrf token.
Same thing can be concluded with session->logout->new_session. Save and fetch everything from the cookie. Since you logged in after a logout in tab2, tab1 will have cookies of tab2 and also the csrf token.

Related

How can I access session data in an EJS template with a node.js backend?

I have created a dynamic menubar based on the user's role. On the server side, when the user is logged in I am storing all their allowed pages in their session.
Now, I have 15 templates. I have included menubar.html file on each and every page. I want to use session data in menubar.html without having to pass it explicitly each time.
I have tried the following code:
app.get('/home',function(req,res)){
res.render('home',{menu : req.session.menubar})
}
app.get('/contact',function(req,res)){
res.render('contact',{menu : req.session.menubar})
}
In above code, I need to pass session data on every page. If I have 50 html templates, it's not efficient to pass the session in each and every route.
What is the best approach to handle this?
Are you including your HTML files as shown in this answer?
Assuming you are using express-session, you may also need to set up middleware to assign session information to the response (res), as shown in this answer.
app.js:
app.use(session({
secret: "sosecret",
saveUninitialized: false,
resave: false
}));
// middleware to make 'user' available to all templates
app.use(function(req, res, next) {
res.locals.user = req.session.user;
next();
});
Then try referring to res.locals.user rather than the request object.
Since EJS is based on escaping to JavaScript, rendering lists can be done using the loop constructs in the language.
To display the value of array on your ejs template, you can use the following code:
<ul class="menubar">
<% for(var i=0; i < menu.length; i++) { %>
<li class="menu-title">
<%= menu[i] %>
</li>
<% } %>
In order to get it on every templates, you need to store the session on database collections first. You use connect-mongo package for that. From the docs:
const session = require('express-session');
const MongoStore = require('connect-mongo')(session);
app.use(session({
secret: 'foo',
store: new MongoStore(options)
}));

Differentiate express-session sessions in chrome tabs

I am trying to assign to each user a "req.session.pseudo", but when I try to connect in different tabs, it display me the same session.
Here is an exemple with a "req.session.page_views".
Here is the code :
var express = require('express');
var session = require('express-session');
var app = express();
app.use(session({
secret: 'ssshhhhh',
resave: false,
saveUninitialized: true
}));
app.get('/', function(req, res) {
if(req.session.page_views){
req.session.page_views++;
res.send("You visited this page " + req.session.page_views + " times");
} else {
req.session.page_views = 1;
res.send("Welcome to this page for the first time!");
}
});
the result on my first tab:
Welcome to this page for the first time!
the result on my second tab:
You visited this page 2 times
I hope to have been clear about my problem. Thank you.
Tabs share cookies, and cookies are used to identify sessions. If tab #1 gets a session cookie, tab #2 will send that same session cookie to the server, so both tabs share the same session.
You can create one session in your browser's "regular" mode, and one session in its "private" (incognito) mode, but that's about it (tabs created in each mode also share the same cookie for that mode, at least in Chrome, so you can't create multiple incognito windows/tabs and create a new session in each).
Possibly, there are extensions for your favorite browser that may be used to create multiple sessions concurrently, but you'd have to search for those.

Connect session undefined when loading new page.

I'm a bit new to sessions and now when I'm trying to use them the session is always undefined for me when coming to the next page after a successful login as an example.
If my login is successful I do the following
var session = req.session;
session.user_id = String(item._id);
session.user_secure = security.security(session.user_id);
Then I redirect to another page:
res.writeHead(302, {
'Location': '/backstageArea'
});
res.end();
And then I try to fin the session again doing this:
var session = req.session;
console.log("uid: " + String(session.user_id));
console.log("hid: " + session.user_secure);
which results in the session being undefined.
these are my last three things in my app.configure:
app.use(express.cookieParser());
app.use(express.session({secret: 'secret', store: store, key: 'sid', cookie:{secure:true}}));
app.use(app.router);
Could it be since I'm redirecting to a new page that I loose the session?
Or am I doing something else wrongly?
By using cookie:{secure:true}, you tell your express application and browsers to send that cookie over https only.
So if you don't use https, you can remove secure: true
Secured cookies are useful feature to protect your sensitive cookies from mand-in-the-middle attack, They are much more useful when you use both http and https on the same domain.
If you want to use https on via nginx or apache, you may need:
app.set('trust proxy', true); // if remove this line, express will refuse to send https cookie to nginx or apache
var session = req.session;
session.user_id = String(item._id);
session.user_secure = security.security(session.user_id);
edit to
req.session.user_id = String(item._id);
req.session.user_secure = security.security(session.user_id);

CSRF token not working when submitting form in express

i'm trying to get forms working in my express app. i have a middleware function that passes the csrf token, req.session._csrf, to res.locals.csrf_token, so the view can use it. now i'm trying to use the local variable in my view and i'm getting a forbidden error from my session middleware.
here's my form code - i'm using handlebars as my templating engine:
<form method='post' action='/api/entries' enctype='multipart/form-data' >
<input type='hidden' name='_csrf' value={{csrf_token}} />
<input class='foo' type='text' />
<input class='bar' type='text' />
<button id='submit' type='submit'> SUBMIT
</form>
i've tried referencing the csrf_token variable with and without the double curly braces and neither works. any ideas on what i am doing wrong? the Error: Forbidden happens before my route function for POSTing to /api/entries is even called. so i'm pretty sure the problem is that i'm doing something wrong with referencing the csrf token..
*edit:*in regards to the "req.session._csrf is deprecated, use req.csrfToken() instead" getting logged to the console, i did:
grep -r '_csrf' .
in my app directory. here was the output.. it doesn't look like i'm referencing it anywhere besides the view, where my hidden CSRF field is named "_csrf"..
./node_modules/express/node_modules/connect/lib/middleware/csrf.js: var secret = req.session._csrfSecret;
./node_modules/express/node_modules/connect/lib/middleware/csrf.js: req.session._csrfSecret = secret;
./node_modules/express/node_modules/connect/lib/middleware/csrf.js: Object.defineProperty(req.session, '_csrf', {
./node_modules/express/node_modules/connect/lib/middleware/csrf.js: console.warn('req.session._csrf is deprecated, use req.csrfToken() instead');
./node_modules/express/node_modules/connect/lib/middleware/csrf.js: return (req.body && req.body._csrf)
./node_modules/express/node_modules/connect/lib/middleware/csrf.js: || (req.query && req.query._csrf)
./v/home.hbs: <input type='hidden' name='_csrf' value={{csrf_token}} />
./v/show.hbs: <input type='hidden' name='_csrf' value={{csrf_token}} />
here is the entire error stack i'm getting when trying to POST to the /api/entries endpoint (i stupidly neglected to mention this before, but i'm using connect-redis for session middleware):
Error: Forbidden
at Object.exports.error (appFolder/node_modules/express/node_modules/connect/lib/utils.js:63:13)
at createToken (appFolder/node_modules/express/node_modules/connect/lib/middleware/csrf.js:82:55)
at Object.handle (appFolder/node_modules/express/node_modules/connect/lib/middleware/csrf.js:48:24)
at next (appFolder/node_modules/express/node_modules/connect/lib/proto.js:193:15)
at next (appFolder/node_modules/express/node_modules/connect/lib/middleware/session.js:318:9)
at appFolder/node_modules/express/node_modules/connect/lib/middleware/session.js:342:9
at appFolder/node_modules/connect-redis/lib/connect-redis.js:101:14
at try_callback (appFolder/node_modules/redis/index.js:580:9)
at RedisClient.return_reply (appFolder/node_modules/redis/index.js:670:13)
at ReplyParser.<anonymous> (appFolder/node_modules/redis/index.js:312:14)
edit 2: the error in connect-redis.js is a function trying to get the current session by the session ID and failing. don't know why this would be happening, my connect-redis setup looks correct. this is killing me
EDIT: If you don't need file uploads, don't use the multipart/form-data enctype. Switching to the default enctype would allow express.csrf() to parse the _csrf token.
In order to parse forms with the multipart/form-data enctype, you need use a multipart parser in your app configuration, or handle file uploads yourself. It's recommended to avoid using the included express.bodyParser() and instead use something like busboy or formidable on the routes you're expecting file uploads, to prevent an exploit.
If you go this route, your _csrf field will no longer be caught by express.csrf() because the form body will not be parsed until after the request passes that middleware. Set your form action to '/api/entries?_csrf={{csrf_token}}' to get around this.
var fs = require('fs');
var async = require('async');
var express = require('express');
var formidable = require('formidable');
var app = express();
app.use(express.urlencoded())
.use(express.json())
.use(express.cookieParser())
.use(express.session())
.use(express.csrf())
app.get('/upload', function(req, res) {
// File uploads ignored.
res.render('upload', {_csrf:req.csrfToken()});
});
app.post('/upload', function(req, res) {
// Explicitly handle uploads
var form = new formidable.IncomingForm();
form.uploadDir = 'temp';
var count = 0;
var maxAllowed = 10;
form.onPart = function(part) {
if (!part.filename) return form.handlePart(part);
count++;
// Ignore any more files.
if (count > maxAllowed) return part.resume();
form.handlePart(part);
};
form.parse(req, function(err, fields, files) {
// Process the files. If you don't need them, delete them.
// Note that you should still reap your temp directory on occasion.
async.map(Object.keys(files), function(key, cb) {
fs.unlink(files[key].path, cb);
}, function(err) {
res.end();
});
});
});
CSRF syntax has changed slightly in the latest versions of Express/Connect. You now want your middleware to look like this:
.use(express.csrf())
.use(function (req, res, next) {
res.cookie('XSRF-TOKEN', req.csrfToken());
res.locals.csrftoken = req.csrfToken();
next();
})
For testing your code, note that you first need to GET the form page in order to generate the CSRF token. Only then will your POST succeed. If it fails, you need to reload the page in the browser before trying to POST again.
I too hit this problem today and it's taken several hours for me to find a solution. Hopefully this answer helps someone with my exact problem. As #amagumori, I'm using redis for session handling and express 3.4.8, connect-redis 1.4.7.
Basically I was able to determine that the order of my express configuration affects the number of times a new token was issued. It seemed like everything being served out of public was creating a new token.
Specifically in my case I had to move the calls
app.use(express.methodOverride());
app.use(express.bodyParser());
app.use(express.static(__dirname + '/public'));
above
app.use(express.csrf());
app.use(function(req, res, next){
res.locals.token = req.csrfToken();
next();
});
and tokens are issued as expected for sessions.

Not cookie based session management in node.js

I am looking for a non-cookie based session management in node.js, something like pass a parameter in the URL like &session_id=. It will know that a session has expired when a request comes with an session_id. I've looked at connect library, but it looks that it is cookie based only.
Warning
Passing the session id as a GET parameter is considered bad practice. Why? It is dangerous because people don't usually care about session id and they will probably publish/share links with their session ids inside.
It's also a problem because when a user clicks an external link on your web, and goes to another site, that new site will be able to see the session_id in the referrer link.
So I don't think it is a good idea. Cookies are more secure.
Have a look at: Session Hijacking
For every request you receive, you will get all of the client cookies accordingly.
You can also set client cookies in the response HTTP headers using "Set-Cookie."
Using a GET parameter is unsafe. Any user could accidently share their session ID, but if you want 100% security, I'd share session IDs via cookies, and I would use HTTPS to prevent snoopers from stealing cookies.
You can use localstorage or sessionStorage..
almost same as cookie
not a cookie
better than a cookie!
More info: https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Storage
It's very -very- easy to use... in Js for example:
<script>
// check if Storage is avaible
if(typeof(Storage)!=="undefined") {
// Save data to local storage (no exiparion date)
localStorage.setItem("name_always", "bxx");
// Save data to the current session (removes when the tab is closed)
sessionStorage.setItem("name_now", "bxx");
} else {
// No Storage support...
}
// Access to stored data
alert( "For only now, my name is: " + sessionStorage.getItem("name_now"));
alert( "Every day, my name is: " + localStorage.getItem("name_always"));
</script>
Tags: javascript html5 local-storage session-storage
You can use sessions with a store in node.js. For example, you have express application and want to use session like system in your webapp. You can use connect-mongo module for this. This will let you store your session in db. In your app.js
var express = require('express'),
, mongoStore = require('connect-mongo')(express);
var app = express();
app.configure('all', function () {
app.use(express.session({
secret: "terces",
cookie: { maxAge: 24 * 60 * 60 * 1000 },
store: new mongoStore({
url: your_db_url
})
}));
app.use(function(req, res, next) {
res.locals.session = req.session;
next();
});
});
With basic code above, you have session in express that you can use it in your controllers and views directly. In your controller;
app.post('/blog/create/?', function(req, res, next) {
if (!req.session.user) {
next("You need to login in order to create blog!");
}
});
In your view, you can use session.user in order to generate profile menu for example.

Resources