So I'm trying to store some info in the session, for later use. But when I fetch it in react, it doesn't all come back. Despite the get route having all of the data.
My thought process is on the serverside, when a user logs in store their id in the session. I'll then have a /userinfo route that dumps all the data i need in json. I can then fetch that /userinfo route and get the data in the response. My code is as follows:
Heres the post code to sign in, I console.log to verify the session is modified. It is.
User.authenticate(req.body.logemail, req.body.logpassword, function (error, user) {
if (error || !user) {
var err = new Error('Wrong email or password.');
err.status = 401;
return next(err);
} else {
req.session.userId = user._id;
console.log(req.session);
return res.redirect('/');
}
});
Then I have this route:
app.get('/userinfo', (req, res)=>{
res.json(req.session);
});
If I simply go to localhost:3000/userinfo the json looks like:
{
"cookie": {
"originalMaxAge": null,
"expires": null,
"httpOnly": true,
"path": "/"
},
"userId": "blahblahblah"
}
so UserID is indeed there.
However, when I finally go to fetch it in react with:
fetch('http://localhost:3000/userinfo')
.then(res => res.json())
.then(data => console.log(data));
All I get back is the cookie. I dont get UserId. I've tried adding UserId to the cookie as well, just to see if it'd work, and likewise I just get the plain cookie.
Solved the issue myself, I didn't even think about it, but I'm making the request from the client, so when I make the request through fetch()
to
app.get('/userinfo', (req, res)=>{
res.json(req.session);
});
the server is returning the session stored on the clientside, which hasnt been updated with userId. So I need to return session data from the serverside, rather than from the client request.
My solution is to create and maintain a User object on the server-side and when I need to access user data, grab it from the user object.
Related
I am trying to send http requests using axios to my node backend. For some reason, axios keeps returning a 500 (Internal Server Error) even when thunder client (dollar store version of postman) is able to send the request and get a proper response.
index.js (server)
app.get('/api/login', async (req, res) => {
try {
const user = await User.findOne({ email: req.body.email })
if(user===undefined) { res.status(404).json("user not found"); }
const validPassword = await bcrypt.compare(req.body.password, user.password)
!validPassword && res.status(400).json("wrong password")
res.status(200).json(user)
} catch (err) {
res.status(500).json(err)
}
})
Login.js (frontend)
const login = (email, password) => {
console.log(email + ': ' + password)
axios.get('http://localhost:8800/api/login', { email: email, password: password })
.then((response) => console.log(response))
.catch((err) => console.log(err.response))
}
err.response returns no useful data and err.response.data is a blank object. I've tried to edit the request header, but it is already 'application/json'. Again, this request works on thunder client and I made sure that the data I passed in was correct through the console.log(email + ': ' + password . I've been trying to fix this issue for hours so please help. Thank you in advance.
Update: I had previously binded the login function to an onClick to a button, but I put the axios function directly into the brackets instead of login(email, password). The issue persists.
Second Update: I followed the comments' advice and console logged the error on the terminal. It returned TypeError: Cannot read properties of null (reading 'password'). This was strange because in the function, I had console logged password and it returned the proper text. It also says that it cannot find a user within my database that uses the email I am currently using, but even when I pass in the exact email I use in thunder client requests, I still get the error. I think the data is not getting there properly.
Third Update: My hypothesis is confirmed. In index.js, I made the route console log req.body.email and it returned undefined. I passed in an object that I JSON stringified and when console logged in the browser, it returns a proper object. The request is sending an object with undefined properties although I am passing in an object with values
In this case, the issue was that the request was a get request, not a post request. Get requests do not take in data while post requests do. Here is the fix:
index.js (server)
app.post('/api/login', async (req, res) => {
try {
const user = await User.findOne({ email: req.body.email })
if(user===undefined) { res.status(404).json("user not found"); }
const validPassword = await bcrypt.compare(req.body.password, user.password)
!validPassword && res.status(400).json("wrong password")
res.status(200).json(user)
} catch (err) {
res.status(500).json(err)
}
})
If you have to receive the request parameters in body (mainly in json format) then you have to go with POST type request.
In the GET type request, you can get request parameters in the form of params and query string.
Parameters may be either part of path:
myapi/customers/123
or a query string:
myapi?customer=123
More details can be found here:
https://www.restapitutorial.com/lessons/httpmethods.html
I wanna be able to destroy a session after the user logs out. At the moment it's not working as expected. The session doesn't get destroyed.
I'm able to print the console.log() from the logout route.
That's the code I've used so far:
Frontend
const handleLogout = async (e) => {
e.preventDefault();
try {
await fetch("http://localhost:4000/logout", {
method: "GET",
});
} catch (error) {
console.error(error.message);
}
};
Backend
app.get("/logout", (req, res) => {
req.session.destroy((err) => {
if (err) {
return console.log(err);
}
res.send("logged out");
console.log("logged out");
});
});
I don't know if this helps but here is the session:
P.S. I'm using react, node, express and express-session.
Thanks in advance!
req.session.destory() removes the session id from the server-side session store and that will render the client logged out since the session id is removed from the server-side session store and can no longer match the client cookie on future requests. It does not, however, remove the now-orphaned session cookie from the browser.
To do that, you need a res.clearCookie(cookieName) before you do the res.send("logged out"); where you target the name of whatever your session cookie is. That will send the proper header with the response to tell the browser to clear that cookie.
I am currently developing an API in NodeJS and a WebClient in VueJS.
I want to create a simple login/logout mechanism. The Webclient should send the request to the API and the API should handle the sessions of the different users and serving the data from its mongoDB.
Recently I came across a strange problem. When I want to login via WebClient, the browser shows me that it sends two different headers to the API. One "OPTIONS" header and one "POST" header. The POST header is sent due to my POST-Request (WebClient), which is clear. Due to Mozillas explenation I also understand the OPTION header part since the browser wants to know if the API's CORS-configuration has been configured contrary for the WebClient or not (or something like this).
But the problem now is the following:
Due to the two different header-methods, my API creates two session-IDs with just one login-post action (via WebClient), whereas one of these two sessions gets detached from the WebClient, unnecessarily consuming valuable space. This only happens through the WebClient. Using PostMan does not show this behaviour, only one session will be created since only one header is sent.
What I want to know is:
Since there is a reason for why the OPTIONS-header is sent, I want to know how I can prevent my API to create the second session via the WebClient.
Since this problem happened after testing my WebClient, it is clear to me that the WebClient is not configured or written properly, but I cannot tell where or how to prevent this since WebDev at this level is new to me. Like: Do I have to configure my WebClient or the API?
If more code is needed just tell me what you need and I will edit this post and attach the neede code.
//////////////////// Code:
//// API:
// src/main.js:
const corsOptions = {
origin: "http://localhost:8080",
credentials:true,
methods: "GET,HEAD,POST",
preflightContinue: false,
optionsSuccessStatus: 204
};
// src/routes/LoginRoute.js:
router.post("/login", function(req,res,next){
console.log("// Routes/Login");
if(!req.user){
console.log("---- Info: User is not logged in");
passport.authenticate("local", (err, user, info) => {
if (err) {
return next(err);
}
if (!user) {
return res.status(401).json({success:false,errors:["User not found"]});
}
req.login(user, (err) => {
if(err){
return next(err);
}
console.log("---- Info: Routing success");
return res.status(200).json({success:true});
});
})(req, res, next);
}else{
console.log("---- Info: User is already logged in");
return res.status(403).json({success:false,errors:["Already logged in"]});
}
});
//// VueJS
// src/store/index.js
actions:{
authenticate({commit},formData){
console.log("---- Info: Vuex/Store/Action/Authenticate - Trying to log in");
var url = "http://localhost:3000/api/login";
console.log(formData);
return Vue.axios.post(url,formData)
.then((res)=>{
console.log("---- Info: Vuex/Store/Action/Authenticate - Success");
commit('login',res.data);
return res.data;
})
.catch((err)=>{
console.log("---- Error: Vuex/Store/Action/Authenticate");
console.log(err);
// commit('logout');
return err;
});
}
}
//////////////////// FireFox Network Analysis:
I would believe that the cors package you apparently use would resolve this problem.
In any case this is not a problem with your frontend, it's handled by your backend and it's typical that the browser creates problems that aren't present with Postman. Postman is built to not care about browser CORS issues. You can however set a session in Postman for testing: https://blog.postman.com/sessions-faq/
Back to the problem: One approach is a middleware function for all or specific routes to filter out requests that already contain a session.
app.use((req, res, next) => {
if (req.session) {
// bypass new session creation, re-route or other solution
}
next()
})
Another and more flexible approach is to target the OPTIONS header directly. I solved a similar problem in a serverless proxy function with a request handler that targets the OPTIONS header specifically. It filters such requests out and returns an "OK signal" and generous headers to tell the browser it can go ahead with the real request, the POST.
You could try something like this as a general middleware or add it as a response to certain endpoints (code not tested, just freestyling the Express syntax here):
const optionsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type'
}
exports.reqHandler = async(req, res) => {
if (req.method === 'OPTIONS') {
return {
res.set(optionsHeaders)
res.status(200)
}
}
req.session.user = {}
req.session.user.id = uuid.v4()
// etc. ...
return res.status(200).json({
success: true,
data: req.session.user
msg: 'Session ID set'
})
}
So I figured out what to do in order to prevent the browser to send two headers to the API server.
I had to configure the axios.post() overgive a header option to the funciton:
const axiosHeaders = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
};
Vue.axios.post(url,QueryString.stringify({username:username}),axiosHeaders)
.then(..)
[...]
Setting the "Configure-Type" as "application/x-www-form-urlencoded" will do the work, but the form-data has to be wrapped with the QueryString.stringify(..) function.
With this, the browser stops to send multiple headers with one post-request and in my case, it stops to create multiple sessions on my API.
I hope this will be usefull for anyone else too.
I am trying to build a RESTful API using Node.js w/ Express. I am fairly new to the MEAN stack, and want to use best practices. The concept I'm having trouble grasping and implementing is the following:
Restricting routes like PUT and DELETE on a user object, to only allow requests from users who 'own' this object.
One possibility I've thought of:
Creating secret token for users that matches token in DB
So when creating a user I assign them a token, store this in the DB and attach it to their session data.
Then my middleware would look something like:
router.put('/api/users/:user_id', function(req, res, next) {
// already unclear how this token should be transfered
var token = req.headers['x-access-token'] || req.session.token;
// update user (PUT /api/users/:user_id)
User.findById(req.params.user_id, function(err, user) {
if (err) {
res.send(err);
} else if (user.token != token) {
res.json({ sucess: false, message: 'User not same as authenticated user.' });
} else {
// set new information only if present in request
if (req.body.name) user.name = req.body.name;
if (req.body.username) user.username = req.body.username;
...
// save user
user.save(function(err) {
if (err) res.send(err);
// return message
res.json({ message: 'User updated.' });
});
}
});
Questions I have regarding best practice
Is the scenario I thought of at all plausible?
What data should I use to create a unique token for a user?
Is storing the token in the session the best solution?
Sidenote
This is a learning project for me, and I am aware of libraries like Passport.js. I want to learn the fundamentals first.
I have a repo for this project if you need to see some of the surrounding code I'm using: https://github.com/messerli90/node-api-ownership
Edit
I would accept a good RESTful API book recommendation, where these points are covered, as an answer.
Edit 2
I actually found a lot of the answers I was looking for in this tutorial: http://scottksmith.com/blog/2014/05/29/beer-locker-building-a-restful-api-with-node-passport/
I was trying to do this without the use of passport.js but a lot of the concepts covered in the article made some of the mechanics of an authorized API clear to me.
If I understand your question, this is an API, and the client (not a browser) is passing the secret token (api key) in the request, in a header. Seems reasonable. Of course, you must require https to protect the api key. And, you should have a way for users to revoke/regenerate their API key.
So far, I don't think you need to store anything in the session. It seems like storing the token in the session just complicates things. Presumably, if you are going to establish a session, the client has to include the token in the first request. So, why not just require it on each request and forget the session? I think this makes life simpler for the api client.
A 'bit' too late, but if someone is still looking for an answer, here is how i did it:
router.put('/', function(req, res) {
var token = req.headers['x-access-token'];
if (!token) return res.status(401).send({auth:false, message:'No token provided'});
jwt.verify (token, process.env.SECRET, function (err, decoded) {
if(err) return res.status(500).send({auth:false, message:'failed to auth token'});
User.findByIdAndUpdate({_id: decoded.user_id}, req.body, function(err, user) {
if (err)
res.send(err);
res.json({username: user.username, email: user.email});
});
});
});
Just pass the user id that is stored in the token to the mongoose function. This way the user who sent the request can only update or delete the model with his ID.
Reading material:
Implementing Access Control in Node.JS
Found this super clear article on how to allow users to only delete replies they own. Hope it helps.
What worked for me:
.delete(requireAuth, async (req, res, next) => {
const knexInstance = req.app.get("db");
const comment = await CommentsService.getById(knexInstance, req.params.id);
if (comment === undefined) {
return res.status(404).json({
error: {
message: `Comment doesn't exist.`
},
});
}
if (comment.users_id !== req.users.id) {
return res.status(401).json({
error: {
message: `You can only delete your own comments.`
},
});
}
CommentsService.deleteComment(knexInstance, req.params.id)
.then((numRowsAffected) => {
res.status(204).end();
})
.catch(next);
})
Update at bottom!
My node.js server uses express.js to manage sessions. Upon login, I store some user information in req.session. I have a logout endpoint that simply deletes the user data from req.session before sending its response.
With every request the user makes, I use authentication middleware to make sure there is still user data in the session, so deleting user data in the session object should fail any subsequent authentication.
To test my server, I have a nodeunit test that logs in, calls a few endpoints, logs out, and then attempts to call another endpoint. I would expect the last endpoint to fail in authentication because I previously blew away user data. Instead, when I make the last call, my user data is still there. It's as if the logout call that deleted it was not written back into the session store.
Here's my app.js:
app.use(express.cookieParser('secretPassword'));
app.use(express.cookieSession({key: 'someKey'}));
...
app.get('/logout', accounts.logout);
app.get('/account', auth.authenticateSession, accounts.show);
auth.js:
exports.authenticateSession = function(req, res, next) {
if (!req.session.user) {
return res.json(401, {
error: 'Access denied. You must be logged in to make this request.'
});
}
...
}
accounts.js:logout:
exports.logout = function(req, res) {
req.session.user = null;
res.send('Logged out');
};
Unit tests:
step1_logIn : function(test) {
var postData = qs.stringify({
accountname: 'testAcct',
accountpassword: 'hello'
});
ct.postAndCall('/login', null, postData, function(resData, res) {
myCookie = res.headers['set-cookie'];
test.ok(res.statusCode === 200);
test.done();
});
},
step2_logout : function(test) {
ct.getAndCall('/logout', myCookie, function(data, res) {
test.ok(data === 'Logged out.');
test.ok(res.statusCode === 200);
test.done();
});
},
step3_ensureLoggedOut: function(test) {
ct.getAndCall('/account', myCookie, function(data, res) {
test.ok(res.statusCode === 401);
test.done();
});
}
When the tests run, execution goes through logout successfully, then into authenticateSession on the call to /account and at this point, req.session.user still exists! Why!?
Is my cookie store giving me stale data?
Is there a way to force express to save my session data manually, or do I just modify the req.session object and trust that it's going to save it?
Update:
It looks like this problem is directly related to the app middleware surrounding cookies. When I use app.use(express.session()) instead of app.use(express.cookieSession(...)), the session data is properly blown away and my tests pass.
I figured it out. Apparently express.cookieSession(...) is meant to be a set-once type of storage. Subsequent requests will have access to the session data that was initially set, but changing the req.session object won't save back new session data.
To fix the problem, I switched over to use express.session(...) which uses a server-side store for session vars.