0

I'm fairly new to node.js and trying to make a simple website which first asks the authentication and then redirects the user to a page.

so, what i do is that i create a middleware which listenes to every request made to my website.

what this middleware does that it checks if the the user is logged in with my website or not is yes then redirect to the requested page if not, then redirect to the login page, here is my code for that.

var express = require('express');
var app = express();

// middleware for using static files 
app.use('/public', express.static(__dirname + '/public')); // all the js files for check_before.html
app.use('/templates', express.static(__dirname + '/templates')); // here are css/js files for login.html 

// setting up views folder
app.set('views', __dirname + '/views'); // check_before.html is sitting here
app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');

app.use((req, res, next) => {
    res.render('check_before.html'); 
// here in the html I implement the logic using js files which are located in public folder.

    next();

});

// if not logged in , the user gets here
app.get('/login', (req, res, next) => {

    res.render('login.html')

});

// if logged in redirect to some page 
app.get('/welcome_page', (req, res) => {
    return 'welcome'

});

everything goes well untill the user hits the http://localhost:8000/login page (after the check if they are signed in or not) the page keeps on loading multiple times and it won't stop reloading. I have defined all the css, js files of login.html page in the templates folder which is loaded above the middleware by reffereing to this question Express middleware getting called many times. could that be a problem?

what could be the reason for this?

here is the error i'm getting in the console.

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client

any guesses?

Edit1
I went through this question Error: Can't set headers after they are sent to the client , and i guess it concludes that setting headers explicitly could be problematic.

Could this be a reason? because in my logic if the user is not signed In, I'm just using window.location.replace('http://localhost:8000/login') to redirect the user to login page. should I use any another method for redirection?

Edit2

There are suggestions that i must write a middleware to check is the user is authenticated or not, and get a sort of flag for that, but as i've stated above that i'm implementing the logic in check_before.html(client side). so it won't be possible to use that.

P.hunter
  • 1,345
  • 2
  • 21
  • 45
  • From expressjs.com docs - https://expressjs.com/en/4x/api.html#res.render - try using `res.render('index', function(err, html) { res.send(html); });`. PS - `res.send()` will add `res.end()` which is needed to close the connection to the client – Deryck Feb 24 '18 at 07:48
  • @Deryck the error still persists, I've updated my question with the error code – P.hunter Feb 24 '18 at 07:52
  • That is probably because you have `res.render()` in the `app.use` just before `app.get('/login' ...`. You may need to simply *include* that `check_before` file inside your template instead of trying to render it here – Deryck Feb 24 '18 at 07:58
  • @Deryck I've updated my question again moreover what should i use instead of render? If i don't use render then how would the middleware would respond ? how would it get know that it is (check_before.html) is located there .. – P.hunter Feb 24 '18 at 08:00
  • If `check_before` is strictly for checking that the user is logged in, see https://github.com/expressjs/session – Deryck Feb 24 '18 at 08:02
  • @Deryck how can i use session in this? – P.hunter Feb 24 '18 at 08:08
  • Sorry I also meant to say dont use `next()` after `res.render()` – Deryck Feb 24 '18 at 08:10
  • no use it still gets reloaded multiple times.. – P.hunter Feb 24 '18 at 08:12
  • follow Miguel's answer. You'll need to handle authentication in another middleware and alter the `req` object as needed. Or you can do `if ( ...authenticateUserMethod() ) { res.render(...) } else { next(); }` where `authenticateUserMethod()` is of course whatever you make to authenticate the user. – Deryck Feb 24 '18 at 08:16
  • as i said in my question that i'm implementing the "authenticateUserMethod()" in the client side so how can i even use it in the server side? – P.hunter Feb 24 '18 at 08:18
  • Do not use the client-side to authenticate a user. This would let me visit whatever URL I want just by reading your code. – Deryck Feb 24 '18 at 08:24
  • i was using `gapi` and `firebase authentication` for that so that i dont think that might be a issue what do you think? – P.hunter Feb 24 '18 at 08:26
  • client-side: call server-side authentication URL (firebase.com). server-side: do the authentication and set session, etc. then set `req.isAuthenticated = true;` and do `next();` and then check for `req.isAuthenticated` in your next route (`/welcome_page`) client-side: correct page is rendered, nothing else to do – Deryck Feb 24 '18 at 08:29
  • i thought for the exact same solution but the only thing i got stuck into was that how i'm supposed to listen which url in my website the user has requested for? do you mean that i have to check re.isAuthenticated for each route of my website? – P.hunter Feb 24 '18 at 08:37
  • like with `app.use()` i can listen for any url the user has asked for and then do the authentication check and after that i redirect to user to the requested url.. – P.hunter Feb 24 '18 at 08:40
  • @Deryck i guess your idea about about the server side authentication is awesome, and now i'm going to use passport for that, not only for checking the user / listening but also in the login page, thanks – P.hunter Feb 24 '18 at 09:32

2 Answers2

2

I have two guesses:

  1. You shouldn't call send (or any other function )after res.render.

  2. Middleware to verify user is logged in should be something like this (applied only to routes you want to verify user)

Middleware should be something like this

const isAuthenticated = (req, res, next) => {
    if(req.isAuthenticated()) {
        next();
    } else {
        res.redirect('/');
    }
}    

app.get('/welcome_page', isAuthenticated, (req, res) => {
  return 'welcome'

});
Miguel Giménez
  • 425
  • 4
  • 10
  • pardon me but i'm not able to get it. what does `req.isAuthenticated()` mean? does it mean that i'm supposed to implement the logic there? – P.hunter Feb 24 '18 at 08:12
  • i'm actually using `window.location.replace('http://localhost:8000/login')` is the user is signed in. – P.hunter Feb 24 '18 at 08:13
  • if you use a middleware like express-session and passport it will add that method on every request after login. if you are not using passport you can use express-session and call the req.login() method after login in which will add a cookie to the request with the user object , so if the user is logged in te request object will allso have a user object (if (req.user) //then its authenticated.) – Miguel Giménez Feb 24 '18 at 08:27
  • the `Cannot set headers after they are sent to the client ` is because you are calling other methods after res.render , you shouldnt do that – Miguel Giménez Feb 24 '18 at 08:28
  • so like after login i create a cookie and check every time user requests with any url/ – P.hunter Feb 24 '18 at 08:33
  • sorry actually req.login is a passport method – Miguel Giménez Feb 24 '18 at 08:35
  • but yeah after login you should create a cookie which should be added to the request – Miguel Giménez Feb 24 '18 at 08:36
  • i thought of this approach too as discussed in the comment section of the question with Deryck but the problem is that how i'm supposed to listen for the requested urls by the user? – P.hunter Feb 24 '18 at 08:39
  • like with `app.use()` i can listen for any url the user has asked for and then do the authentication check and after that i redirect to user to the requested url.. – P.hunter Feb 24 '18 at 08:40
  • I think that what you should do is add the verify user is logged in middleware to the routes that are meant to be user specific like so : `app.get('/profile', isAuthenticated, (req, res) => { return 'welcome' });` and the other ones just add them without the middleware `app.get('/login', (req, res) => { res.render('login.html') });` – Miguel Giménez Feb 24 '18 at 08:50
  • yea, actually it is what i'm thinking right now, actually i'm using google sign in client library and firebase authentication as my logic in client side, so what i'm thinking is that, i'll use that library for the server side as well using passport i guess and right a authentication script to set something like `isAuthenticated` function and in all the routes i call/check that before i respond to the user with their requested url.. – P.hunter Feb 24 '18 at 08:55
  • okay so rather than calling i can put it as the second argument right? and it accepts true or false , if it is true the user can access that route and vice versa.. – P.hunter Feb 24 '18 at 08:57
  • so the second argument will be a a callback , which if the user is logged in, will call the `next()` function which will call the next element on the middleware chain ,( the third argument if it exists), or the rest of the middleware specified after this one... If the user is not logged in will redirect the response and not call the rest of the middleware – Miguel Giménez Feb 24 '18 at 09:04
  • so what does `req.isAuthenticated()` mean? i couldn't get it as the const `isAuthenticated` is a callback what does it mean to call it twice? – P.hunter Feb 24 '18 at 09:09
  • sorry should have called them differently, its two completely different methods , the const isAuthenticated is the one weve created and the other one is a cookie added by passport to the req object (which you wont have if you are not using passport) – Miguel Giménez Feb 24 '18 at 09:19
0

The reason is that middleware is called before your /login request. To fix it, you need to modify your middleware function. It should be something like:

app.use((req, res, next) => {
    if(isLoggedIn) {  //isLoggedIn is a flag that checks whetehr user is logged-in or not
        res.render('check_before.html');
    } else {
        // here in the html I implement the logic using js files which are located in public folder.

        next();
    }
});
HOTAM SINGH
  • 121
  • 2
  • 11
  • i guess you haven't read the edited question, actually i implement the logic in client side, which means that i cant use the flag in the server side.. – P.hunter Feb 24 '18 at 08:18
  • It is good practice to have validation on both sides, client and server wise – Laurens Mäkel Feb 24 '18 at 08:26