I have this code to load an admin page in Node.js file.
app.get('/admin',function(req,res){
if(req.session.loggedIn == undefined)
{
res.writeHead(301, {
Location: '/login'
});
res.end();
}
else
{
res.send("ADMIN PAGE");
}
});
I will have several pages routed like this, where I check on the session if the user is logged in. If not, I'll redirect to the login page, if the user is logged I show the page.
Can I some how add the restriction to all pages (Except for the login page, of course), without repeating the code, to avoid changing in all routes if in the future, I have to add a new restriction?
Maybe I can use a regex right? but then how can I decide what to do in each rule?
Just create a middleware function and include that wherever you need it. For example:
function requireLoggedIn(req, res, next) {
if (!req.session || req.session.loggedIn === undefined) {
res.writeHead(301, { 'Location': '/login' });
res.end();
return;
}
next();
}
Then just use it in your routes wherever it makes sense, like:
app.get('/admin', requireLoggedIn, function(req, res) {
res.send('ADMIN PAGE');
});
Or you could use it in a separate router, pass it to app.use(), etc.
Related
What is the best way to call a function on many but not all requests in a node express app? (An example would be a function which checks if the user is currently logged in)
What I did is to define a module exporting a checkLogin(...) function and to call this function on each corresponding api-request. E.g.:
Module auth:
module.exports = {
checkLogin: function(req, res, next) {
if (req.session.hasOwnProperty('user')) {
//if the user is logged in we pass through
next();
} else if (req.cookies.user == undefined || req.cookies.pass == undefined) {
res.render('login', { title: 'Login' });
} else {
User.checkLogin(req.cookies.user, req.cookies.pass, true, function(o) {
if (o != null) {
req.session.user = o;
next();
} else {
res.render('login', { title: 'Login' });
return;
}
});
}
}
};
Routes for /index:
//...
var auth = require('../middlewares/auth.js');
//...
router.get('/index', auth.checkLogin, function(req, res) {
//if we passed the auth.checkLogin step we render the index page
res.render('index', {
title: 'Index',
udata: req.session.user
});
});
In another route file:
//...
var auth = require('../middlewares/auth.js');
//...
router.get('/user/someAPICall', auth.checkLogin, function(req, res) {
...
});
Is this the way to go or are there better ways to do that? I could define a middleware function which I could include using app.use(function(){..}) in each route. The problem is that every request for this route would go through this function which is not what I want.
Routers (http://expressjs.com/en/guide/routing.html) are a great way to design your application. You could think of your URL paths as namespaces, and create a router for the namespace that requires user authentication.
Most likely your main /index page won't require immediate redirecting to login, since it's used for presentation purposes; but if required, then just include the auth.checkLogin as you did above.
For everything else where you need your user to be authenticated (e.g. everything under /user/*), you'd better create a scoped router
const router = express.Router();
router.use(auth.checkLogin);
router.get('/someAPICall', fn1, fn2);
router.get('/someOtherAPICall', fn3, fn4);
and then in your parent router or main app, just include the router:
app.use('/user', router);
which is just like defining:
app.use('/user/someAPICall', [auth.checkLogin, fn1, fn2]);
app.use('/user/someOtherAPICall', [auth.checkLogin, fn3, fn3]);
This gives you the advantage of creating modular route handlers - which makes them easier to adjust, reuse, etc. - and at the same time will keep auth.checkLogin, although always executed when the router is entered, just for the paths defined by the router.
In short, the approach would be: "execute function on all routes inside the router, but not on all the app requests".
If you cannot redesign your routes in this way, then yes, you'll always need to include auth.checkLogin in handlers list for the paths you only want to use.
I'm trying to add a configuration value that allows us to take down the site temporarily when needed. It works, but for some reason it is not rendering the error page I've defined in my 503 route. I'm seeing "Service Unavailable. Redirecting to /503", but it doesn't ever redirect and doesn't display the page. Can anyone tell me what I'm missing here?
Middleware:
// Maintenance mode
app.use(function (req, res, next) {
if (config.globals.maintenanceModeEnabled) {
// Need this condition to avoid redirect loop
if (req.url !== '/503') {
res.redirect(503, '/503');
} else {
next();
}
} else {
next();
}
});
Route:
// Down for maintenance page
router.get('/503', function (req, res) {
res.status(503).render('common/503', {layout: 'base'});
});
I have the following program to log in with Google:
app.get('/oauth/google', function(req, res) {
res.redirect(<OAUTH2_URL>);
});
app.get('/oauth/google/callback', function(req, res, next) {
var code = req.query.code;
if(!code || !_.isString(code)) {
return next(new Error(400, 'Invalid code'));
}
.
.
.
// I try the code to see if it is valid.
});
How do I only allow Googles redirect back to the application to have access to the callback route, and block regular users from using it?
If you're using sessions then you could set a flag from your /oauth/google path before you redirect off to Google, and then on your /oauth/google/callback simply check for that flag, and reset.
app.get('/oauth/google', function(req, res) {
req.session.authFlag = true;
res.redirect(<OAUTH2_URL>);
});
app.get('/oauth/google/callback', function(req, res, next) {
if (!req.session.authFlag) return next(new Error(403, 'Forbidden'));
else req.session.authFlag = false;
...
});
If you're not using sessions though, or for some reason sessions aren't available because the client doesn't support cookies (which should be a concern in above mentioned solution as well!), then I guess your best bet is to just check for req.query.code because other than that query string (req.query.code) there's no difference between requests redirected by Google and direct requests made by regular user.
(...req.headers.referer/origin could've worked in theory but they're unreliable and shouldn't be used as a measure)
function redit (req, res, next) {
var session = req.session.user;
if (session) {
res.redirect('/home');
next();
}
else {
res.redirect('/');
next();
}
}
app.get('/', redit, function (req, res){
res.render('home0.ejs');
});
I code this middleware to check if there's a req.session.user, if there is, the user would be redirected to home, if not, would be redirected to /. But when this middleware is called, Chrome says to me Error 310 (net::ERR_TOO_MANY_REDIRECTS)', any solutions...?
You miss the fact that after redirect an anonymous user (with falsy req.session.user value) will end up at the same (/) page - so their identity will be checked up by redir middleware again... and again... and again. Hence the 'TOO MANY REDIRECTS' error.
The common solution is to redirect all the anonymouses to some other gateway page - and that page obviously should NOT check session.user.
I'm using Express and Passport OpenID Google strategy and I would like to set returnURL on each auth request to be able to return to the page that initiated that auth.
The situation is that I have HTML5 slides application with Node.js backend (and with social stuff and editor and Portal and extensions... https://github.com/bubersson/humla) and I want be able to log in user on some slide (via slide menu...) but then I want him to get back to same slide easily.
So I would need something like this?
app.get('/auth/google', function(req,res) {
var cust = "http://localhost:1338/"+req.params.xxx;
passport.authenticate('google', returnURL:cust, function ...
}
I've read Passport's guide, but still don't know how to do that. I know this wouldn't be safe, but how else could I do it?
Or how can I make the application to return to the page from where the login has been initiated? Or is there a way to make OpenID authentication using AJAX (and still be able to use passport as well)?
I've figured this out for my apps Twitter authentication, I am sure that the GoogleStrategy is quite similar. Try a variant of this:
Assuming you have defined the route for the callback from the authentication service like so (from the passport guide):
app.get('/auth/twitter/callback',
passport.authenticate('twitter', {
successRedirect: authenticationRedirect(req, '/account')
, failureRedirect: '/'
})
);
Just change that block to this:
app.get('/auth/twitter/callback', function(req, res, next){
passport.authenticate('twitter', function(err, user, info){
// This is the default destination upon successful login.
var redirectUrl = '/account';
if (err) { return next(err); }
if (!user) { return res.redirect('/'); }
// If we have previously stored a redirectUrl, use that,
// otherwise, use the default.
if (req.session.redirectUrl) {
redirectUrl = req.session.redirectUrl;
req.session.redirectUrl = null;
}
req.logIn(user, function(err){
if (err) { return next(err); }
});
res.redirect(redirectUrl);
})(req, res, next);
});
Now, define your middleware for authenticated routes to store the original URL in the session like this:
ensureAuthenticated = function (req, res, next) {
if (req.isAuthenticated()) { return next(); }
// If the user is not authenticated, then we will start the authentication
// process. Before we do, let's store this originally requested URL in the
// session so we know where to return the user later.
req.session.redirectUrl = req.url;
// Resume normal authentication...
logger.info('User is not authenticated.');
req.flash("warn", "You must be logged-in to do that.");
res.redirect('/');
}
Works!
Wherever you have your login button, append the request's current URL as a
query parameter (adjust for whatever templating system you use):
<a href='/auth/google?redirect=<%= req.url %>'>Log In</a>
Then, add middleware to your GET /auth/google handler that stores this value in
req.session:
app.get('/auth/google', function(req, res, next) {
req.session.redirect = req.query.redirect;
next();
}, passport.authenticate('google'));
Finally, in your callback handler, redirect to the URL stored in the session:
app.get('/auth/google/callback', passport.authenticate('google',
failureRedirect: '/'
), function (req, res) {
res.redirect(req.session.redirect || '/');
delete req.session.redirect;
});
Try res.redirect('back'); in the callback for passport.authenticate
According to the author this isn't possible with OpenID strategies. We managed to update these dynamically by directly accessing the variables:
app.get('/auth/google', function(req, res, next) {
passport._strategies['google']._relyingParty.returnUrl = 'http://localhost:3000/test';
passport._strategies['google']._relyingParty.realm = 'http://localhost:3000';
passport.authenticate('google')(req, res, next);
});