Express resources with authentication middleware? - javascript

Passport.js offers great authentication for node.js and Express including a middleware solution:
ensureAuthenticated = function(req, res, next) {
if (req.isAuthenticated()) {
return next();
}
return res.redirect("/login");
};
How can I use this middleware in the express-resource module? Unfortunately,
app.resource('users', ensureAuthenticated, require('./resources/users'));
doesn't work.

I know this is a little too late, and the original post was answered, however, I was looking for the same answer and found a solution I thought others might want to know.
Just make sure ensureAuthenticated is called from passport.
app.resource('users', passport.ensureAuthenticated, require('./resources/users'));
It is found here: https://gist.github.com/1941301

Workaround. Ensure authentication on all requests and ignore requests going to /auth and /auth/callback.
app.all('*', function(req, res, next) {
if (/^\/auth/g.test(req.url)) {
return next();
} else if (req.isAuthenticated()) {
return next();
} else {
return next(new Error(401));
}
});

You will need to do some magic in order to have each resource use the authentication middleware. The following gist explains how to accomplish this by using a menu structure.
https://gist.github.com/3610934
Essentially, you need the following logic:
app.all('/' + item.name + '*', ensureAuthenticated, function (req, res, next) {
next();
});
You could then in your menu structure specify what resources are protected, and if they require any kind of specific permissions, even down to the HTTP method level.
Hopefully this helps someone!

I'm not sure if express-resource has the option of inserting middleware into specific resources, but you could always insert checking if you are in that resource inside the middleware.
ensureAuthenticated = function(req, res, next) {
if (!/^users/.test(req.url) || req.isAuthenticated()) {
return next();
}
return res.redirect("/login");
};

This works:
app.get('/', ensureAuthenticated, admin.index);
As long as your strategy is setup correctly. I'm using the local strategy. Check out the guide:
http://passportjs.org/guide/username-password.html

I was looking up this topic as well, and found https://npmjs.org/package/express-resource-middleware. Haven't tested it, though.

Related

Express router.use not working

I used express-generator to generate the basic structure of an express app.
I have this in routes/my.js:
router.use('/update', function(req, res, next) {
req.headers['content-type'] = 'application/json';
console.log('router use invoked');
next();
});
router.get('/update', function(req, res, next) {
console.log('=== GET /my/update', req.body);
});
router.post('/update', function(req, res, next) {
console.log('=== POST /my/update', req.body);
});
And in app.js I added:
var my = require('./routes/my');
app.use('/', routes);
app.use('/users', users);
app.use('/my', my);
It's working fine for GET, and the router middle-ware is actually working (console.log is called), but the headers is not being set to app/json (as the log message in post outputs the body to be empty).
I can solve this by adding this before any other middle-ware
app.use('/my/update', function(req, res, next) {
req.headers['content-type'] = 'application/json';
next()
});
And it works. I tried moving the app.use('my', my); line before any other middle-ware but it stopped working altogether.
What can I do in order to give priority to my router.use?
You're adding the header to the request object, not the response. Also the below is the preferred way to do it.
router.use('/update', function(req, res, next) {
res.header('content-type', 'application/json');
console.log('router use invoked');
next();
});
ExpressJS is all about working with middle wares.
According to official documentation here, Middleware is a function with access to the request object (req), the response object (res), and the next middleware in line in the request-response cycle of an Express application, commonly denoted by a variable named next.
Middleware can:
Execute any code
Make changes to the request and the response objects
End the request-response cycle
Call the next middleware in the stack
I would like to point a few things about your my.js router, if you don't want to send any response back then it should be something like :
router.get('/update', function(req, res, next) {
res.end();
});
router.post('/update', function(req, res, next) {
res.end();
});
You must end your request, response cycle else it will be left hanging. Practically you would like to send some data(preferably JSON data) back to client so you can use res.json() or res.send(). You don't need to set application/json headers if you use res.json().

Node.js detect if a variable exists in req for every page a user goes to

More specifically, I have an auth system that uses passportjs and req.user is defined if the user is authenticated.
Right now my website only has about 5 pages, but it's growing, and at the top of every route, I check if req.user exists and I pass a true or false variable to the rendered template, and the template renders accordingly.
I messed around with things such as app.get("*") but I didn't end up finding anything good.
How could I check if req.user (or anything else that could exist within req...) exists -- when a user goes to any page of my website, without repeting code?
Progress:
Using this code in app.js:
app.use(function (req, res, next) {
// Using req.locals.isAuthenticated would be better, as it's automatically passed to every rendered templates.
req.context = {};
req.context.isLoggedIn = req.isAuthenticated();
// req.locals.isAuthenticated = req.isAuthenticated();
next();
});
app.use('/dashboard', dashboard);
and this in the routes/dashboard route:
router.get('/', function (req, res) {
res.render('dashboard', { isLoggedIn: req.context.isLoggedIn });
});
Works - I can then see if the user is logged in by doing for example {{ isLoggedIn }}.
However when I uncomment the req.locals line in the first code snippet, I get a 500 error.
Two things to note:
Usually when your application needs to do something for a bunch of different pages, you want to setup a middleware function via app.use
Express has a res.locals variable whose properties will be included in any rendered template
With the above points in mind, you can construct something like the following:
app.use(function(res, req, next) {
res.locals.isAuthenticated = typeof(req.user) !== 'undefined';
next();
});
You then supply your additional template variables when your routes call res.render. For example:
app.get('/about', function(res, req) {
res.render('about', { 'testValue': 14} );
});
Your template will have access to both isAuthenticated and testValue.
I recommend you put some middleware in place before your route handlers but after passport's.
app.use(function(req, res, next) {
// Create a `context` object for use in any view.
// This allows the context to grow without impacting too much.
req.context = {};
// Assign your flag for authenticated.
req.context.isAuthenticated = typeof req.user !== 'undefined';
// Let the next middleware function perform it's processing.
next();
});
Then you can render each view with the context.
app.use('/', function(req, res) {
res.render('index', req.context); // Context is passed to view for usage.
});
This is all untested code.
You can do it as is already mentioned here ,but in this case you are going to check completely every request. Maybe you have got / you are going to have some pages that don't require any authentification and in this case you have to make some statement that will skip auth for that particular page or you can use something like this:
function checkUser(req, res, next) {
req.userAuth = (req.user !== undefined);
next();
}
app.post("settings", checkUser, doSomething);
app.post("administration", checkUser, doSomething);
app.post("index", doSomething); // Doesn't require any authentification
Or you can straight a way redirect a user
function checkUser(req, res, next) {
if (req.user === undefined) {
res.redirect("/login"); // res.render
}
else {
next();
}
}

Creating express request object as if it came from another URL

There are many ways to mock requests using things like supertest and nock but what I'm looking to do is to be able to create fake request objects, as if the request was going to a different URL, that can be passed on to other processing functions.
app.get("/render", function(req, res) {
// how do I do this?
var fake = createFakeRequest("/bar/?baz=qux", req);
// I want doStuff to believe the request came to /bar/?baz=qux
doStuff(fake, function(err, result) {
res.send(result);
});
});
I'm aware I could modify all of he variables such as req.path, req.query, req.url but I'm worried I may miss something, and it seems like someone out there must have already solved this problem.
The reason I need this behavior is that for any given URL in my CMS, there can be multiple drafts. Each draft will have different content elements, but those individual content elements may have logic based on the URL or query parameters. So even though the request came in as /render/?draft=1&url=/foo/bar/, I want the content element processors to believe the request came in to /foo/bar/ and be oblivious to the version system which actually handled the initial HTTP request.
Not sure to understand but seems like url rewriting, so using a middleware could work
function urlRewrite(req, res, next){
req.url ="...";
next();
}
and so
app.use(urlRewrite);
Be sure to use it at the right place (depending on your server goal)
Cause we maybe need params before the rewrite... and if rewrite, you may need it after...
EDIT
In my framework:
server.before(extractPath);
server.before(urlParams);
server.before(pathParams);
server.get("/foo", route.foo);
So I could write
server.before(extractPath);
=> server.before( function(req, res, next){
urlRewrite(req, res, function(){
extractPath(req, res, next);
}
});
server.before(urlParams);
server.before(pathParams);
server.get("/foo", route.foo);
If urlRewrite depends on urlParams, I could write
server.before(extractPath);
server.before(urlParams);
=> server.before( function(req, res, next){
urlRewrite(req, res, function(){
extractPath(req, res, function(){
urlParams(req, res, next);
});
});
});
server.before(pathParams);
server.get("/foo", route.foo);
As I said, it depends on your framework

Require Authentication for directory (except one page) with Passport.js / Node.js?

I'm new to using Passport.js, but I find it's going pretty well so far. I'm using Passport with passport-local.
However, I want to require authentication for an entire directory excluding one page. So in my node server I'm serving up this direcory like so (using express):
app.use("/admin", express.static(__dirname + "/admin"));
And then I want to let the user hit /admin/login.html, so I wanted to do something like this:
app.get('/gb-admin/login.html', function(req, res){ });
Then I want to require authentication for the rest, so something like this:
app.get('/gb-admin/*', ensureAuthenticated, function(req, res){});
Here is my ensureAuthenticated function, for reference if it helps:
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) { return next(); }
res.redirect('/gb-admin/login.html')
}
How would I go about doing this? I've been generally sending things in infinite loops and causing the browser to timeout. Can anyone help?
The reason you're getting timeouts is because you can't have an empty route handler; at one point, you have to either return a response, or hand the request over the the next route handler/middleware.
That said, try this:
function ensureAuthenticated(req, res, next) {
if (req.path === '/gb-admin/login.html' || req.isAuthenticated()) {
return next();
}
res.redirect('/gb-admin/login.html')
}
app.get('/gb-admin/*', ensureAuthenticated, function(req, res, next) {
next();
});
// the static middleware needs to be declared after the route above, otherwise
// it will take precedence and ensureAuthenticated will never be called.
app.use("/gb-admin", express.static(__dirname + "/admin"));
I don't think there's a way to get it working with a separate route for the login page (unless you actually implement reading login.html and sending it back from without that routes handler), hence the check for it in the ensureAuthenticated middleware.
I wonder if it is your callback. Try:
app.get('/gb-admin/*', function (req, res, next) {
ensureAuthentication(req, res, next) {
if (req.isAuthenticated()) { return next(); }
res.redirect('/gb-admin/login.html')
});
});

Why is PassportJS in Node not removing session on logout

I am having trouble getting my system to log out with PassportJS. It seems the logout route is being called, but its not removing the session. I want it to return 401, if the user is not logged in in specific route. I call authenticateUser to check if user is logged in.
Thanks a lot!
/******* This in index.js *********/
// setup passport for username & passport authentication
adminToolsSetup.setup(passport);
// admin tool login/logout logic
app.post("/adminTool/login",
passport.authenticate('local', {
successRedirect: '/adminTool/index.html',
failureRedirect: '/',
failureFlash: false })
);
app.get('/adminTool/logout', adminToolsSetup.authenticateUser, function(req, res){
console.log("logging out");
console.log(res.user);
req.logout();
res.redirect('/');
});
// ******* This is in adminToolSetup ********
// Setting up user authentication to be using user name and passport as authentication method,
// this function will fetch the user information from the user name, and compare the password for authentication
exports.setup = function(passport) {
setupLocalStrategy(passport);
setupSerialization(passport);
}
function setupLocalStrategy(passport) {
passport.use(new LocalStrategy(
function(username, password, done) {
console.log('validating user login');
dao.retrieveAdminbyName(username, function(err, user) {
if (err) { return done(err); }
if (!user) {
return done(null, false, { message: 'Incorrect username.' });
}
// has password then compare password
var hashedPassword = crypto.createHash('md5').update(password).digest("hex");
if (user.adminPassword != hashedPassword) {
console.log('incorrect password');
return done(null, false, { message: 'Incorrect password.' });
}
console.log('user validated');
return done(null, user);
});
}
));
}
function setupSerialization(passport) {
// serialization
passport.serializeUser(function(user, done) {
console.log("serialize user");
done(null, user.adminId);
});
// de-serialization
passport.deserializeUser(function(id, done) {
dao.retrieveUserById(id, function(err, user) {
console.log("de-serialize user");
done(err, user);
});
});
}
// authenticating the user as needed
exports.authenticateUser = function(req, res, next) {
console.log(req.user);
if (!req.user) {
return res.send("401 unauthorized", 401);
}
next();
}
Brice’s answer is great, but I still noticed an important distinction to make; the Passport guide suggests using .logout() (also aliased as .logOut()) as such:
app.get('/logout', function(req, res){
req.logout();
res.redirect('/'); //Can fire before session is destroyed?
});
But as mentioned above, this is unreliable. I found it behaved as expected when implementing Brice’s suggestion like this:
app.get('/logout', function (req, res){
req.session.destroy(function (err) {
res.redirect('/'); //Inside a callback… bulletproof!
});
});
Hope this helps!
Ran into the same issue. Using req.session.destroy(); instead of req.logout(); works, but I don't know if this is the best practice.
session.destroy may be insufficient, to make sure the user is fully logged out you have to clear session cookie as well.
The issue here is that if your application is also used as an API for a single page app (not recommended but quite common) then there can be some request(s) being processed by express that started before logout and end after logout. If this were the case then this longer running request will restore the session in redis after it was deleted. And because the browser still has the same cookie the next time you open the page you will be successfully logged in.
req.session.destroy(function() {
res.clearCookie('connect.sid');
res.redirect('/');
});
That's the what maybe happening otherwise:
Req 1 (any request) is received
Req 1 loads session from redis to memory
Logout req received
Logout req loads session
Logout req destroys session
Logout req sends redirect to the browser (cookie is not removed)
Req 1 completes processing
Req 1 saves the session from memory to redis
User opens the page without login dialog because both the cookie and the session are in place
Ideally you need to use token authentication for api calls and only use sessions in web app that only loads pages, but even if your web app is only used to obtain api tokens this race condition is still possible.
I was having the same issue, and it turned out to not be a problem with Passport functions at all, but rather in the way I was calling my /logout route. I used fetch to call the route:
(Bad)
fetch('/auth/logout')
.then([other stuff]);
Turns out doing that doesn't send cookies so the session isn't continued and I guess the res.logout() gets applied to a different session? At any rate, doing the following fixes it right up:
(Good)
fetch('/auth/logout', { credentials: 'same-origin' })
.then([other stuff]);
I was having the same issues, capital O fixed it;
app.get('/logout', function (req, res){
req.logOut() // <-- not req.logout();
res.redirect('/')
});
Edit: this is no longer an issue.
I used both req.logout() and req.session.destroy() and works fine.
server.get('/logout', (req, res) => {
req.logout();
req.session.destroy(()=>{
res.redirect('/');
});
});
Just to mention, i use Redis as session store.
I was recently having this same issue and none of the answers fixed the issue for me. Could be wrong but it does seem to have to do with a race condition.
Changing the session details to the options below seems to have fixed the issue for me. I have tested it about 10 times or so now and everything seems to be working correctly.
app.use(session({
secret: 'secret',
saveUninitialized: false,
resave: false
}));
Basically I just changed saveUninitialized and resave from true to false. That seems to have fixed the issue.
Just for reference I'm using the standard req.logout(); method in my logout path. I'm not using the session destroy like other people have mentioned.
app.get('/logout', function(req, res) {
req.logout();
res.redirect('/');
});
None of the answers worked for me so I will share mine
app.use(session({
secret: 'some_secret',
resave: false,
saveUninitialized: false,
cookie: {maxAge: 1000} // this is the key
}))
and
router.get('/logout', (req, res, next) => {
req.logOut()
req.redirect('/')
})
Destroying session by yourself looks weird.
I faced with this issue having next configuration:
"express": "^4.12.3",
"passport": "^0.2.1",
"passport-local": "^1.0.0",
I should say that this configuration works well.
The reason of my issue was in custom sessionStore that I defined here:
app.use(expressSession({
...
store: dbSessionStore,
...
}));
To be sure that your issue here too just comment store line and run without session persisting. If it will work you should dig into your custom session store. In my case set method was defined wrong. When you use req.logout() session store destroy() method not invoked as I thought before. Instead invoked set method with updated session.
Good luck, I hope this answer will help you.
I got an experience that, sometime it's doesn't work because you fail to to setup passport properly.
For example, I do vhost, but on main app I setup passport like this which is wrong.
app.js (why wrong ? please see blockqoute below)
require('./modules/middleware.bodyparser')(app);
require('./modules/middleware.passport')(app);
require('./modules/middleware.session')(app);
require('./modules/app.config.default.js')(app, express);
// default router across domain
app.use('/login', require('./controllers/loginController'));
app.get('/logout', function (req, res) {
req.logout();
res.redirect('/');
});
// vhost setup
app.use(vhost('sub1.somehost.dev', require('./app.host.sub1.js')));
app.use(vhost('somehost.dev', require('./app.host.main.js')));
actually, it must not be able to login, but I manage to do that because, I continue to do more mistake. by putting another passport setup here, so session form app.js available to app.host.sub1.js
app.host.sub1.js
// default app configuration
require('./modules/middleware.passport')(app);
require('./modules/app.config.default.js')(app, express);
So, when I want to logout... it's not work because app.js was do something wrong by start initialize passport.js before express-session.js, which is wrong !!.
However, this code can solved the issues anyway as others mention.
app.js
app.get('/logout', function (req, res) {
req.logout();
req.session.destroy(function (err) {
if (err) {
return next(err);
}
// destroy session data
req.session = null;
// redirect to homepage
res.redirect('/');
});
});
But in my case the correct way is... swap the express-session.js before passport.js
document also mention
Note that enabling session support is entirely optional, though it is
recommended for most applications. If enabled, be sure to use
express.session() before passport.session() to ensure that the login
session is restored in the correct order.
So, resolved logout issue on my case by..
app.js
require('./modules/middleware.bodyparser')(app);
require('./modules/middleware.session')(app);
require('./modules/middleware.passport')(app);
require('./modules/app.config.default.js')(app, express);
// default router across domain
app.use('/login', require('./controllers/loginController'));
app.get('/logout', function (req, res) {
req.logout();
res.redirect('/');
});
app.host.sub1.js
// default app configuration
require('./modules/app.config.default.js')(app, express);
and now req.logout(); is work now.
Apparently there are multiple possible causes of this issue. In my case the problem was wrong order of declarations i.e. the logout endpoint was declared before passport initialization. The right order is:
app.use(passport.initialize());
app.use(passport.session());
app.get('/logout', function(req, res) {
req.logout();
res.redirect('/');
});
simply adding req.logOut(); solved this issue ; "O" should be capitalized
I was having the same issue. Turned out that my version of passport wasn't compatible with Express 4.0. Just need to install an older version.
npm install --save express#3.0.0
This worked for me:
app.get('/user', restrictRoute, function (req, res) {
res.header('Cache-Control', 'no-cache, private, no-store, must-revalidate,
max-stale=0, post-check=0, pre-check=0');
});
It makes sure that your page won't get stored in cache
I'm working with a programmer, that suggests to remove user of req:
app.get('/logout', function (req, res){
req.session.destroy(function (err) {
req.user = null;
res.redirect('/'); //Inside a callback… bulletproof!
});
});
Reason:
we need to remove from req(passportjs also doing this but async way) because there is no use of user data after logout
even this will save memory and also might be passportjs found user data and may create new session and redirect(but not yet happen)
By the ways, this is our responsibility to remove irrelevant thing. PassportJS assign data into req.user after login and also remove if we use req.logout() but it may not works properly some times as NodeJS Asynchronous in nature
I faced the similar problem with Passport 0.3.2.
When I use Custom Callback for the passport login and signup the problem persisted.
The problem was solved by upgrading to Passport 0.4.0 and adding the lines
app.get('/logout', function(req, res) {
req.logOut();
res.redirect('/');
});
Since you are using passport authentication which uses it's own session via the connect.sid cookie this simplest way of dealing with logging out is letting passport handle the session.
app.get('/logout', function(req, res){
if (req.isAuthenticated()) {
req.logOut()
return res.redirect('/') // Handle valid logout
}
return res.status(401) // Handle unauthenticated response
})
All examples here do a redirect after the req.session.destroy.
But do realise that Express will create a new session instantly for the page you are redirecting to.
In combination with Postman I found the strange behaviour that doing a Passport-Login right after the logout gives the effect that Passport is successful but cannot store the user id to the session file. The reason is that Postman needs to update the cookie in all requests for this group, and this takes a while.
Also the redirect in the callback of the destroy does not help.
I solved it by not doing a redirect but just returning a json message.
This is still an issue.
What I did was to use req.session.destroy(function (err) {}); on the server side and on the client side, whenever they logout:
const logout = () => {
const url = '/users/logout'
fetch(url)
setTimeout(function () {
location.reload(); }, 500);
That way, when refreshing the page, the user is without session. Just make sure you are redirecting to the correct page if no one is authenticated.
Not the best approach, perhaps, but it works.
You can try manually regenerating the session:
app.get('/logout', (req, res) => {
req.logOut();
req.session.regenerate(err => {
err && console.log(err);
});
res.redirect('/');
});
This does not remove other data (like passport) from the session.
Try this
app.get('/logout', (req, res) => {
req.logout();
req.session.destroy();
res.redirect('/');
}
I solved this problem by setting the withCredentials: true on my axios.post request to the logout route. I guess the required credentials to identify the session weren't being sent over so the req.logOut() had no effect (I also noticed that req.user was undefined on the log out route, which was a big clue)
I managed to resolve a similar problem by changing the code in my client where I made the request, replacing the following:
const res = await axios.get("http://localhost:4000/auth/logout");
with this:
window.open("http://localhost:4000/auth/logout", "_self");
For me req.logout worked but I don't why req.logout() not working. How function call is not working
I do this
window.open(http://localhost:4000/auth/logout, "_self");
in the function before window.open call to e.preventDefault()
this is recommended because when you do click in log out Button you refresh the page, and the function isn't call it
function logout(e) {
e.preventDefault()
window.open(http://localhost:4000/auth/logout, "_self");
}
3 January 2022
You shoulde be using req.logout() to destroy the session in the browser.
app.get('/logout', function(req, res) {
req.logout();
res.redirect('/'); // whatever the route to your default page is
});
I don't know how but ng-href="/signout" solved my problem. Previously I have used service to logout, but instead I've used it directly.
In my case, using a callback passed to req.session.destroy helped only some of the time and I had to resort to this hack:
req.session.destroy();
setTimeout(function() {
res.redirect "/";
}, 2000);
I don't know why that's the only solution that I've been able to get to work, but unfortunately #JulianLloyd's answer did not work for me consistently.
It may have something to do with the fact that my live login page uses SSL (I haven't been able to reproduce the issue on the staging site or my localhost). There may be something else going on in my app too; I'm using the derby-passport module since my app is using the Derby framework, so it's difficult to isolate the problem.
It's clearly a timing issue because I first tried a timeout of 100 ms, which wasn't sufficient.
Unfortunately I haven't yet found a better solution.

Categories