Post-Redirect-Get in Express Node.js - javascript

I am trying to implement a Post-Redirect-Get design pattern into my code, which uses Express. What I have is:
var sessionVariable;
app.post('/user', function(req, res) {
sessionVariable = req.session;
// Sets a session variable to a token
sessionVariable.token = req.body.token;
// Redirect to /user
return res.redirect('/user')
});
app.get('/user', function(req, res) {
sessionVariable = req.session;
if(sessionVariable.token){
res.send('Logged in');
} else {
res.send('Not logged in');
}
});
What I expect to happen is when the user submits a POST request to /user, it will define sessionVarlable.token, then redirect to /user again where it checks if a token exists.
What ends up happening is I will submit the POST request, and through console loggging I can see that app.post fires, but it does not re-direct. Instead, it seems to get stuck on the page where the POST request was omitted.
What is the reason for this?
Edit: Any res will not fire under app.post, e.g. res.send('test') doesn't work.

I have posted this answer elsewhere: Understanding the "post/redirect/get" pattern
but here is the code that works for me:
app.post('/data', function(req, res) {
data = req.body;
res.redirect('/db.html');
});

Based on the info from the comments, you're making an AJAX request. Instead of making the client redirect from server-side, just do it client-side. Here's an example using Axios on the browser (you can do essentially the same thing with AJAX, just messier):
axios.post('/user', { token: 'tokenhere'}).then(function(response) {
// Status 200 means OK
if (response.status === 200) {
window.location.href = 'user'
}
})

Related

Why does req.sessions return an empty object instead of the passed value?

I'm learning express.js and I'm stuck on one little problem.
How can I save sessions in the browser so I don't have to log in every time. I use cookie-session. I send the login data downloaded from the form for validation using the POST method to endpoint ./login. After successfully passing the validation, it saves sessions in this way: req.session.admin = 1;, and then using redirect, it redirects to the admin endpoint. After redirecting to ./admin, I try to read if there is a previously saved req.session.admin session. The above action returns me undefined, and the req.session itself returns an empty object {}. I tried console.log on the ./login endpoint and yes, everything is OK i.e. it returns 1 and on the ./admin endpoint it is undfined.
My question to you is: Has anything changed recently in the implementation of cookie-session (although I don't think so, because I do it according to the documentation available on npm), or do I need to install some package besides cookie-session?
Save the session
.post('/login', (req, res) => {
const { body } = req;
if (password === body.password && login === body.login) {
req.session.admin = 1;
res.redirect('/admin');
} else {
res.redirect('/login');
}
});
This is where my problem arises, because the session is not saved and each time the validation fails, I return to login
.all('*', (req, res, next) => {
if (!req.session.admin) {
res.redirect('/login');
return;
}
next();
})
Firstly, req.session.admin = 1 does not save session, but it assigns value to separate request. Each request is independent.
You have to return some information to client (e.g token) for further using. Token should be sent in each request in appropriate header and express should verify it.
What's more you should not send credentials in plain text.
What I recommend is to familiarise yourself with JWT concept https://jwt.io/introduction

Value set on the request object in one handler function is missing(undefined) on the next route call

I have a login route that if a user signs in successfully, it attaches the id of the user from the database to the req.session.user property of the request object:
router.route('/za/login')
.get(onlyGuest, (req, res) => {
res.render('login', { layout: false });
})
.post(onlyGuest, async(req, res) => {
try {
const user = await User.findByCredentials(req.body.email, req.body.password);
req.user = user;
req.session.user = req.user._id;
res.redirect('/');
} catch (err) {
console.log(err);
res.status(400).render('login', {
layout: false,
message: {
error: 'login failed!',
email: req.body.email
}
});
}
});
After this property(req.session.user) has been set, a user is directed to the home route. But there is a middleware that allows access to a certain route upon authenticated, otherwise it redirects to another page. Here is the code for the middleware:
let regex = /^\/za(\/.*)?$/; /*a route prefixed with '/za', followed by zero or /anything_else*/
app.use(async (req, res, next) => {
let url = req.originalUrl;
if(!req.session.user && !regex.test(url)) {
console.log('You are trying to access an authenticated route. Login first!');
console.log(req.session.user);
if(url === '/') {
res.redirect('/za');
return;
}
res.redirect('/za/login?forbidden');
return;
}
/*req.session.user property is true or has a value set*/
if (req.session.user) {
try {
const user = await User.findById(req.session.user);
if(!user) {
return res.redirect('/za/login?forbidden');
}
req.user = user;
return next();
} catch (err) {
console.log(err);
return res.render('errors/500');
}
}
});
After successfully logging in via the login route(providing the correct credentials), i wonder why the request.session.user is still undefined and therefore redirecting me to a page accessible by unauthenticated users.
I have a feeling that i am missing the timing of order of execution. But this is just a feeling.Actually i don't know where i may have errored.
Short answer: You have to set the session or token or whatever you use for authentication on the client side.
Long answer:
You have the req object and the res object available in your handlers. The req object is the request and data sent by the client (web browser). The res object is what is sent back to the client as a response. As soon, as you call any terminating method on the res object like res.render or res.send, the handling of the specific client request is done and the linked objects are "thrown away". Therefore, setting any value on the req object does never affect the client, as the req object never gets sent back to the client. Modifying the req object would only be of use if you want to access some values on the req object in another middleware dealing with the same request.
So, you have to send the user id in the response object back to the client upon successful authentication and on the client side, take this id and set it e.g. as permanent header to be sent along with every request sent from the client from now on.
Hint: This way of authentication is not very secure. Usually, you would generate a token - e.g. a JSON Web Token (JWT-token) on the server-side upon successful authentication, send this token to the client and verify this token on each subsequent request.
Hint 2: Search (on Stackoverflow) for e.g. "node.js authentication" to get more input.

Error: Can't set headers after they are sent Node.js and ExpressJS

I am using Node.js and ExpressJS to save a mongoose model. I am getting the error of Error: Can't set headers after they are sent. I think it has to do with the line res.redirect("/dashboard/it/model"); conflicting with setRedirect({auth: '/login'}), from my route, the code in the setRedirect is below labeled. The setRedirect is from a middleware called middleware-responder that came as part of the template I am using to create Stripe user accounts. I can not remove it as far as I know. my GitHub repo I have committed all files that are not test files and are relevant (no unneeded views ect than what is already there)
Save Model
if(type=="aps"){
var newAccessPoint = {
name: name,
manufacturer: manufacturer,
model: model,
range: range,
bands: bands,
channel: channel,
poe: poe,
notes: notes,
signout: signout,
author:author
};
// Create a new access point and save to DB
AP.create(newAccessPoint, function(err, newlyCreated){
if(err){
console.log(err);
} else {
//redirect back to models page
res.redirect("/dashboard/it/model");
}
});
}
Route
app.post('/dashboard/it/model/new',
setRender('dashboard/it/modelCreate'),
setRedirect({auth: '/login'}),
isAuthenticated,
dashboard.getDefault,
(req, res) => {
setRedirect code
exports.setRedirect = function(options){
return function(req, res, next){
if(req.redirect){
req.redirect = _.merge(req.redirect, options);
} else {
req.redirect = options;
}
next();
};
};
setRender code
exports.setRender = function(view){
return function(req, res, next){
req.render = view;
next();
};
};
That's happening because you are trying to send a response to the client when you already closed the connection.
Hard to tell by the way you are showing us your code but it seems like you are redirecting to options and then in the same request you are redirecting to dashboard/it/model
I pull your code from github.
I think the error message was clear. in your getDefault() middleware you are rendering a response so the server start sending data to your client and just after you try to redirect him to another url. Thats why when your comment out that middleware all work nicely.

Why does the browser return unexpected end of input when I can get the API response from browser?

I have a very basic react front end and a very basic nodejs server.
I'm trying to validate the username and password on the server when the client clicks submit and then eventually redirect them to their dashboard.
Right now I am not able to go any further than sending the request for user data.
This is my very basic authentication function on the server, functions like authenticate and searchusers are not asynchronous.
// Authorization
app.get('/auth/:userId/:hash', function(req, res){
var id = req.params.userId.toString();
var hash = req.params.hash.toString();
var error = {
err: 'something went wrong!'
}
var user = dm.searchUser(id, users);
if (!user){
res.json(error);
}
var isAuthenticated = dm.authenticate(user, hash);
if (!isAuthenticated){
res.json(error);
}
if(isAuthenticated){
console.log('authed')
res.json(user);
}
});
and this is my client side react component method that handles the submit action of a form.
submit(e){
e.preventDefault();
const BASE_URL = "http://localhost:3001/auth/"
fetch(`${BASE_URL}${this.state.userId}/${this.state.password}`, {
method: 'GET',
mode: 'no-cors'
})
.then(response=>response.json())
.then(json=>{console.log(json)})
.catch((err)=>{
console.log(err)
})
}
From what I have tried, if I tried to send a request from my browser, the server does respond with my JSON data.
But when I try to do the same with my client, it gives back an error, can someone please help me out with what is going wrong?
[EDIT]
This is the error I get

Express user authentication middleware, how much should it do?

I'm trying to learn Express session and authentication handling.
For example:
app.post('/login', authCredentials, function(req, res) {
console.log("second")
});
function authCredentials(req, res, next) {
//this happens first
console.log(req.body) // => { username: etc, password: etc }
next();
}
My question is just how much should my authCredentials function do?
For example if the credentials are correct, I can do something like
res.redirect('/index'). Once I do that, however, what purpose does the second function have?
Other questions:
How would I handle invalid credentials?
If I make authCredentials just return true or false depending on the credentials, doesn't that break the middleware flow because it would never invoke next()?
Is it possible to access anything in authCredentials in the anonymous callback after it? Basically in the function(req, res) { }?
The answer depends on your authentication strategy i.e. are you using session identifiers, access tokens, etc.
In either case I suggest that you break out the credential exchange (aka login) from the authentication.
function usernamePasswordExchange(req,res,next){
var username = req.body.username;
var password = req.body.password;
callToAuthService(username,password,function(err,user){
if(err){
next(err); // bad password, user doesn’t exist, etc
}else{
/*
this part depends on your application. do you use
sessions or access tokens? you need to send the user
something that they can use for authentication on
subsequent requests
*/
res.end(/* send something */);
}
});
}
function authenticate(req,res,next){
/*
read the cookie, access token, etc.
verify that it is legit and then find
the user that it’s associated with
*/
validateRequestAndGetUser(req,function(err,user){
if(err){
next(err); // session expired, tampered, revoked
}else{
req.user = user;
next();
}
});
}
app.post('/login',usernamePasswordExchange);
app.get('/protected-resource',authenticate,function(req,res,next){
/*
If we are here we know the user is authenticated and we
can know who the user is by referencing req.user
*/
});
Disclaimer: I work at Stormpath and we spend a lot of time writing
authentication code :) I just wrote our newest library, stormpath-sdk-express,
which has a concrete implementation of my suggestions
You want to add your authCredentials middleware to every end point that needs authentication. app.post('/login') usually does not need any as you want to access this end point to actually get credentials in the first place.
When credentials are correct/valid you simply invoke next() and the workflow will jump to the next middleware or the actual end point. If there was an error, invoke next() with an error object like next(new Error('could not authenticate'); for instance. Add an error route to your general routing and the error will be handled there:
app.use(function(err, req, res, next) {
res.render('error', err);
});
Should be answered by now.
A middleware does not return a value. It either calls next() or ends the process differently by calling res.send().
There are different approaches to pass variables from one middleware to another. The most common is probably to attach the desired value to the req parameter.
authenticate is an asychronous function in the following example:
function authCredentials(req, res, next) {
authenticate(req.body, function(err, user) {
if (err) {
return next(err);
}
req.user = user;
next();
});
}

Categories

Resources