This part of code caused the problem to my app.
router.use(function(req, res, next){
if(req.user){
res.locals.username = req.user.username
}else{
res.redirect('/login');
}
next();
});
router.get('/', function(req, res, next) {
res.render('dashboard');
});
If I removed the first block, everything worked fine. But the first part have to be there, to act as the auth middleware. But it crashed and I got error of below:
_http_outgoing.js:341
throw new Error('Can\'t set headers after they are sent.');
^
Error: Can't set headers after they are sent.
Try returning immediately after performing the redirect() to avoid further route handler executions that may also try to set headers. You will also want to check req.url before redirecting. For example:
router.use(function(req, res, next){
if (req.user) {
res.locals.username = req.user.username
} else if (!/\/login/i.test(req.url)) {
return res.redirect('/login');
}
next();
});
This will prevent execution from continuing if you are redirecting the request (redirect() sends (the appropriate) headers).
There are 2 weak points in the code you provided:
It redirect() the request and then continue processing it as if nothing happened. It should stop processing the request, in other words not call next() after redirect, as #mscdex correctly pointed out.
It always redirects the user to the /login page if the user is not provided. Even if the user is requesting /login, creating endless cycles of redirects: /login -> [no user] -> [redirect /login] -> /login -> [no user] -> ...
The most common pattern to handle user authorization is this:
// middleware to check user authorization
function checkAuth(req, res, next){
if(req.user){
res.locals.username = req.user.username
next(); // authorization handled successfully, continue processing request
}else{
res.redirect('/login');
// finish processing request without calling next()
}
}
// we add checkAuth middleware to prevent unauthorized users from accessing server root ('/')
router.get('/', checkAuth, function(req, res, next) {
res.render('dashboard');
});
// but we don't need to check user authorization on '/login'
router.get('/login', function(req, res, next) {
res.render('...');
});
Related
I'm doing a web app with nodeJS and for the authentication I'm using PassportJs with passport-local-mongoose.
I make this snippet for the root route to check if the user is logged in
app.get('/', function(req, res){
if(req.isAuthenticated()){
List.find({}, function(err, results){
if(!err){
res.render('home');
}});
}else{
res.redirect('/login');
}
});
so my question is: there's a way to redirect the user to the login page if they are non-logged, from all URLs without specifying it in all the routes get.
Express comes up with middlewares. Basically, a request pass through all middlewares that match its path and http method. You can do whatever you want in a middleware, then either send a response or pass the request to the next middleware.
// All get requests
app.get('*', function(req, res, next){
// If not authenticated
if(!req.isAuthenticated()) {
res.redirect('/login');
}
// Else, go to the next middleware
next();
}
// Real business here
app.get('/', function(req, res){
List.find({}, function(err, results){
if(!err){
res.render('home');
}});
}
});
You can read more here : https://expressjs.com/en/guide/using-middleware.html
I quite don't understand the difference between these two:
app.get('*', function(req, res, next) {
next();
//ROUTE 1
});
app.get('*', function(req, res) {
res.redirect('/some');
//ROUTE 2
});
app.get('/some', function(req, res) {
res.send("success");
//ROUTE 3
});
When I try making request to ROUTE 1, I get response success but ROUTE 2 doesn't show this response. Why is that?
What I want to do is:
Every request should pass from ROUTE 1 and the control should be handled to a SPECIFIC route, which I would write in it ROUTE if-else statement (not like next(), which sends control to next MATCHING route).
For example:
app.get('*', function(req, res, next) {
if(x==y){
//call SPECIFIC route 3
} else {
// call SPECIFIC route 4 (another route)
//ROUTE 1
});
I tried to do it with redirect but it's not working.
Thank you.
EDIT:
Routes would be: /checkIfSession exists. I would use express-session to check if user's username exists or not in session.
If exists, I want to send control to if otherwise else.
Assume the requests are:
http://198.168.43.200:3000/checkIfSession
http://198.168.43.200:3000/some
(I will call only 1st request).
EDIT 2: I tried following but I don't get any response when I request:
app.use(function (req, res, next) {
if(2==2){
res.redirect("/session");
} else {
res.end("else");
}
});
app.get("/session", function(req, res){
res.write("session");
res.end();
});
app.get("/some", function(req, res){
res.write("some");
res.end();
});
Request: /some
I suppose if you want your routes to go through some kind of authentication first you can use middleware in your routes.
Below is sample code:
app.get('/some', checkSession, function(req, res) {
res.send("success");
});
// this is the middleware function
function checkSession(req, res, next) {
// do your checking here
if (x===y) {
next();
//continue users access to protected API
}
else {
res.redirect('/');
// redirect user to the default login page
}
}
In this above example there are 2 Cases
Case1:
x === y as from your given example I'am assuming users is logged in, so when the user is accessing /some section of your website he will receive Success from the server.
This is the use of your next() function i.e. it continues the execution of that api or sends the data whatever the user is requesting. Something similar to continue in your programming.
Case2:
x!==y now this will be the case where user is not authenticated or logged in and user is still trying to access the /some section of your website. In this case user will be redirected to login page of whatever you have designed for your website for him/her to re-enter his/her credentials.
Here your redirect function redirects the user without sending any data. Something similar to the break.
I'm using express framework , Lets say I have this line in the API :
router.delete('/user',(req, res) => { //deleting...}
Now I want that only an Admin will be able to access this line.
In the rest of the code there are lines that only user can access like :
router.put('/post')
And lines only admin can access like:
router.put('/killDB)
what is the best way (tokens, sessions or something like that) that will be able to help me differenitate between the two?
Use password to authenticate users and then check if the user is an admin. And then simply add password logic to your route. Below I will provide my code where I just check if user is logged in (it was enough for me)
router.get('/delete', isLoggedIn, function (req, res) {
Page.collection.drop();
var page = new Page();
page.save(function (err) {
if(err) throw err;
res.redirect('/admin');
});
});
// render login form
router.get('/login', function (req, res) {
res.render('login',{ message: req.flash('error'), layout: null});
});
// process the login form
router.post('/login', passport.authenticate('local-login', {
successRedirect : '/admin', // redirect to the secure profile section
failureRedirect : '/login', // redirect back to the signup page if there is an error
failureFlash : true // allow flash messages
}));
router.get('/logout', function(req, res) {
req.logout();
res.redirect('/');
});
function isLoggedIn(req, res, next) {
// if user is authenticated in the session, carry on
if (req.isAuthenticated())
return next();
// if they aren't redirect them to the home page
res.redirect('/');
}
You can use the connect-roles package to authorize your users, and then route them to those URL's which they are allowed to access.
You can also opt for passport.js, however it is more or like a authentication package where as the connect-roles just aims at to provide only the "authorization" part. And this package works well with Express.
Once you implement this package, you can use the "role" attribute to check the user's authorization level and allow them to perform their respective actions.
For eg.,
if (req.user.role === 'admin') {
router.put('/killDB)
}
You can check out the package here: https://www.npmjs.com/package/connect-roles
Hope this helps!
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().
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')
});
});