Express redirecting doesn't change req.url - node.js

I have a route that redirects upon successful login
app.post('/login', function(req, res){
if(req.body.password === Server.cfg.auth.userPass) {
req.session.user = {nick: req.body.username, pass: req.body.password}
res.redirect('/chat')
} else {
res.render('user/login', { locals: { error: 'Invalid password' } })
}
})
The redirect seems to work as the page is refreshed with the correctly rendered jade file. However, the url still says /login and my pageTitle variable (being set through template vars) does not change either. If I refresh the page after the redirect, everything changes to the way it should be. It is only after the redirect that it does not change.

This has got to be a pretty common mix up for folks trying to deal with ajax redirects coming from a server controlled development background. My example shows what happens if authorization fails, slightly different; but you can use the same concept of intercepting the response and checking status, etc., and let the client JavaScript do the redirect.
My client code is actually a backbone model but in turn is calling jquery's ajax like:
model.save({ error:function...
Server
function requiresLogin(req, res, next) {
if(req.session.user) {
next();
} else {
//res.redirect('/sessions/new?redir=' + req.url); // won't work
res.send("Unauthorized", 403); // send 403 http status
}
}
Client
// Assumes you can get http status from response
error: function(resp, status, xhr)...
if(resp.status === 403) {
window.location = '/sessions/new'; // optionally put a redirLastPage=somewhere
}
This works as desired for me. I'd also suggest googling ajax post redirects to see why this

Looks like this is a jQuery problem. At least it was for me. You can override it with rel=external. More info at http://jquerymobile.com/demos/1.1.0/docs/pages/page-navmodel.html.

Related

res.redirect() does not end request and causes error: "Cannot set headers after they are sent to the client "

My route that handles get requests for the login page checks to see if the user is already logged in. If they are, redirect them to /userprofile, if not, render the login.ejs view.
From what I was reading, res.redirect() should end the http request, but in this example I've been getting some weird behavior. When the user is logged in, I get the error "Cannot set headers after they are sent to the client" multiple times. Here's the code:
app.get("/login", (req, res) => {
if (req.session.user) { res.redirect('/userprofile') };
res.render('login');
})
But I realized that if
I just include a return before res.redirect() or
negate the if statement and invert the order of the responses
the code works fine.
Could anyone explain why? Thanks
It is caused by you redirecting the user, to /userprofile before rendering login. A way you could avoid this would be to do the redirect frontend, in your fetch.
fetch('login', {
method: 'POST',
})
.then(() => {
window.location.href = '/userprofile'
})

Express JS - Redirecting If MongoDB id doesn't exist / Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client

Within my Posts routes, i'm making it so that the user can go to posts/:id to retrieve information about the post with that ID, however i'm trying to make sure that if the user goes to a post that doesn't exist, he gets redirected back to the posts index route. My issue is that the server is going through the entire router.get function instead of redirecting the user and stopping.
router.get("/:id", async (req, res) => {
if (!mongoose.isValidObjectId(req.params.id)) {
console.log("Test1")
res.redirect("/")
res.end()
}
console.log("Test2")
try {
console.log("Test3")
const post = await Post.findById(req.params.id)
if (post == null) res.redirect("/")
res.render("posts/show", { post: post })
}
catch {
console.log("Test4")
res.redirect("posts/index")
res.end()
}
})
If I go to the route posts/weiqeiqwie (which isn't valid) it prints all of the test console logs. What can I do for the code to return after the error?
I'm still a bit new to using express, so sorry for the dumb question.
you cant redirect AND render at the same time.
this is what this part of the code does when post is null :
if (post == null) res.redirect("/")
res.render("posts/show", { post: post })
adapt it like this, so that now when post is null it only does the redirect part.
(never forget the return)
if (post == null) return res.redirect("/");
res.render("posts/show", { post: post })
the same goes for the redirect line 3 :
it should be :
if (!mongoose.isValidObjectId(req.params.id)) {
console.log("Test1")
return res.redirect("/")
}
also res.end() is not required. you can remove it from everywhere.

Fetch/React not redirecting with Node/Express res.redirect()

On my React front-end, I make a fetch() GET call to my Node/Express server. The Node/Express server checks if response has a certain cookie, and if not should redirect it. My code is something like this:
React code
componentDidMount(){
fetch('/example',{redirect: "follow"})
.then(data => data.json())
.then(res => {
//do something with response
}
})
}
Node/Express:
router.use((req, res, next) => {
let { token } = req.cookies;
if(token){
next();
}
else{
res.redirect(303, '/login');
}
});
router.get('/example', (req, res) =>{
//sends a json response back
})
It might be important to note that I am using react-router-dom as a browser router, thought I'm not sure what this does to server redirects. I've searched and saw people who said to set redirect to "follow". However, even when setting status to a 3xx and setting redirect to "follow", the page still doesn't redirect properly. Instead, it receives a response with 200 OK status(which I don't understand how it could have changed, unless Node/Express didn't actually set it to '303'), redirected: true, and a response.url pointing to the redirect url, but still stays on the same page. I've made sure that the cookie isn't there, so that's not it. I know I can probably check response.redirected every time and manually set window.location = response.url, but I still don't understand why the redirect did not work. Do I have to set it manually everytime then?

Node redirect after saving to db

Im doing a node app, which has a html form doing an action to /users
This url calls this method on post
exports.create = function(req, res, next) {
const user = new User(req.body);
user.save((err) => {
if (err) {
return next(err);
} else {
res.status(302).json(user).redirect('/chat');
}
});
};
However i'm unable to do a redirect after storing the data in my db, and get the error Can't set headers after they are sent.
I've tried placing the redirect different places in the form, but i keep getting the same error.
Try removing .json(user) from the redirect. You cannot both send a JSON and a redirect at the same time.
Or if you want to send the JSON, maybe as a response to an Ajax request, don't send .status(302) but do the redirect on client side JavaScript.

Express redirect error: can't set headers after they are sent

When this code hits the redirect line it throws the 'Can't set headers after they are sent error' and doesn't redirect. I'm guilty of not fully understanding headers and how express works with them. This link about this error is confusing me a bit, probably because I don't have a basic enough understanding of what's going on. Also, I know this is a bit of a naive approach to authenticating, but I'm just trying to get basic things to work.
app.post('/api/login', function(req, res) {
if (req.body.password === auth.password) {
auth.date = new Date()
res.redirect('/admin')
} else {
console.log("wrong pw")
}
})
UPDATE : thank you to #Brendan Ashworth I missed an obvious else, which I've added now and no longer get the error.
However this line doesn't change the contents of my page
res.sendfile('./public/admin/views/tunes.html')
It worked before I wrapped it with the auth check
var auth = require('../config/auth')
module.exports = function(app) {
/*
* CONTENT API
*/
//...
/*
* Admin Routes
*/
app.get('/admin/login', function(req, res) {
res.sendfile('./public/admin/views/login.html')
})
app.post('/api/login', function(req, res) {
if (req.body.password === auth.password) {
auth.date = new Date()
res.redirect('/admin')
} else {
res.json({message: 'Wrong password!'})
}
})
app.get('/admin', function(req, res) {
if (auth.date) {
res.sendfile('./public/admin/views/tunes.html')
console.log("test") //
} else { //added else
res.redirect('/admin/login')
}
})
app.get('/admin/:url', function(req, res) {
if (auth.date) {
res.sendfile('./public/admin/views/' + req.params.url + '.html')
} else { //added else
res.redirect('/admin/login')
}
})
// frontend routes
// route to handle all angular requests
app.get('*', function(req, res) {
res.sendfile('./public/views/index.html')
})
}
FINAL UPDATE!! The last thing I needed was to handle the redirect client side after sending the file. Simple authentication works perfectly now!
$http.post('/api/login', $scope.auth).success(function() {
window.location.href = '/admin'
})
An explanation of the error Can't set headers after they are sent error:
All HTTP responses follow this basic structure:
.. Response Line ..
.. Headers ..
.. Body ..
If you want to redirect a user, first the Response Line will be sent with a redirect code (lets say 300), then the Headers will be sent with a Location: xxx header.
Then, we can finally send a body (not in the case of a redirect, but in general). However - in the case with your code - you are sending a Body response then trying to redirect the user. Since the headers (and response line) have both already been sent (because you sent the body), it can't send more headers after the body.
An example of this in your code would be:
app.get('/admin', function(req, res) {
if (auth.date) {
res.sendfile('./public/admin/views/tunes.html')
}
res.redirect('/admin/login')
})
If I'm assuming right, you actually want to return after the res.sendfile() call. If auth.date is truthy, then you'll be sending a file (i.e. body response) and then giving a redirect code - that doesn't work.
after redirect just call res.stop();

Resources