I have some questions regarding login and sessions. I have this code:
The db query:
login: function(req,callback) {
var query = 'SELECT id FROM users WHERE email = "' + req.body.email_login + '" AND password = "' + hashlib.sha1(req.body.password_login) + '" LIMIT 1';
client.query(query, callback);
}
The route:
app.post('/login', function(req, res, next) {
users.login(req,function(err, results) {
if (err) {
res.render('index');
} else if (results[0]) {
req.session.userdata = results[0];
req.session.is_logged_in = true;
res.render('site/news');
}
}
}
Auth middleware:
var auth = function (req, res, next) {
if (req.session.userdata && req.session.is_logged_in === true) {
next();
} else {
res.redirect('/');
}
}
I use db store for the session.
Now my questions are:
1) Is this a safe way to do it? Or should I consider doing it some other way?
2) Say I have this URL /domain/users/1, where the last segment is the user id which is used to fetch user data.
And on that view I have a form for changing user data. Is it safe to check if the user id matches the session user id and then show the form?
In the view:
// e.g. get the session.id from dynamichelper
if (data.userid === session.userdata.id) {
// The form where user can change his data contained within here
}
The server is going to use SSL.
Thanks in advance
George
In the db query code, check for req.body.email_login and req.body.password_login to make sure they're not null and that they're strings. Someone could sent an empty response and that will generate an Internal Error on your side.
Also in the route, you might want to log the error and redirect the user to the /500.html page (internal error):
if (err) {
console.log(error);
res.redirect('500');
} else ...
You shouldn't do this in the view:
if(data.userid === session.userdata.id) { //The form where user can change his data contained within here }
Try instead to achieve this in the model (preferably), make a function for it and pass only one parameter to the view like so:
res.render('view', { loggedIn: true });
The function from the model:
function checkUser(id, session) {
return (userid === session.userdata.id);
}
...
module.exports.checkUser = checkUser;
You can call it from the route like so (for ex):
res.render('view', { loggedIn: model.checkUser(req.body.id, req.session); }
You might also want to look at http://passportjs.org/
Related
I have a route to check if a user is logged in. It works well, but I don't understand what is the problem if I create a second route just below that calls it just to do the same thing. It seems like I can't access the cookie anymore in the second route, but I don't know why. Thanks for your help !
// This route works :
router.get('/loggedin', async (req, res) => {
try {
const token = req.cookies.jwt;
console.log("token : " + token) // Token is correct here in loggedin route, but is undefined if I use the route below
const decodedToken = jwt.verify(token, process.env.JWT_SECRET);
if (decodedToken) {
res.send(true);
}
else {
res.send(false);
}
}
catch (err) {
res.status(500).send(false);
}
});
// This route calls the route above and doesn't work
router.get('/loggedinbyanotherway', async (req, res) => {
const checking = await fetch(`${process.env.API_URL}:${process.env.PORT || 3000}/loggedin`)
console.log(checking.ok) // Returns false
const data = await checking.json()
console.log(data) // Returns false
res.send(data)
});
Your fetch request isn't providing any cookies, so how could the code handling the request read any cookies?
More to the point... This entire operation is unnecessary. Why make an HTTP request to the application you're already using? Instead, extract the functionality you want into a common function and just call that function from both routes. For example:
const isLoggedIn = (req) => {
const token = req.cookies.jwt;
const decodedToken = jwt.verify(token, process.env.JWT_SECRET);
if (decodedToken) {
return true;
} else {
return false;
}
};
router.get('/loggedin', async (req, res) => {
try {
res.send(isLoggedIn(req));
}
catch (err) {
res.status(500).send(false);
}
});
router.get('/loggedinbyanotherway', async (req, res) => {
const checking = isLoggedIn(req);
res.send(checking);
});
In the example it's not really clear why you need the second route or what else it offers, but I can only assume it's just a placeholder for some additional functionality you plan to add.
Either way, the point is that the application doesn't need to make an entire HTTP request to itself, since you're already in that application and have access to the same logic.
GET method
Use accessToken of an authenticated user.
Only non-admin account can proceed.
User should be able to retrieve his orders only.
Router
router.get("/my-orders", auth.verify, (req, res) => {
const user = auth.decode(req.headers.authorization);
if (!user.isAdmin) {
UserController.getMyOrders(req.body).then(getMine => res.send(getMine));
} else {
return res.status(403).send("Access denied.");
}
});```
Controller
module.exports.getMyOrders = (body) => {
return User.find({}, {
"isAdmin": 0,
"_id": 0,
"password": 0
});
}
I am getting everything. Can someone help me code how to filter the user where the token belongs and retrieve his orders and not able to get other users' orders?
By passing an empty object in your .find method, you are telling mongodb to look for everything. I'm assuming in body you have some data to find a specific user, if so you would use that. eg. if body contains a username, you would write...
module.exports.getMyOrders = (body) => {
return User.find({username: body.username});
}
Here is some more info on db.collection.find()
EDIT - Look up user by JWT:
router.get("/my-orders", auth.verify, (req, res) => {
//Here you have decoded your JWT and saved it as user
const user = auth.decode(req.headers.authorization);
if (!user.isAdmin) {
//here you are passing user instead of req.body
UserController.getMyOrders(user).then(getMine => res.send(getMine));
} else {
return res.status(403).send("Access denied.");
}
});
module.exports.getMyOrders = (user) => {
//now you are using 'username' from the decoded jwt to look up the user
return User.find({username: user.username});
}
My application is a process of authenticating with 2 services, one of which is AWS Cognito. When a user is authenticated (so has the JWT Tokens that Cognito provides), I attempt to list the groups for that user via the AdminListGroupsForUser API call. I was getting the UserNotFoundException which was odd as the previous call had just authenticated that user with the same credentials?
I experimented with the following:
router.post("/groups/list", (req, res, next) => {
const { email, limit, nextToken } = req.body;
const listGroupsForUserParams = getAdminListGroupsForUserParams({
email,
limit,
nextToken
});
const getUserParams = getAdminGetUserParams(email);
cognitoClient.adminListGroupsForUser(listGroupsForUserParams, (listErr, listData) => {
cognitoClient.adminGetUser(getUserParams, (getErr, getData) => {
console.log(listErr); // "UserNotFoundException"
console.log(listData); // null
console.log(getErr); // null
console.log(getData); // User
});
});
});
listGroupsForUserParams and getUserParams contain the same information which is:
{
UserPoolId: "...",
Username: "test#example.com" // I use email as Username
}
I don't get how this makes sense that the former call cannot find the user in the pool but the latter can?
See (for references):
AdminListGroupsForUser
AdminGetUser
I had the same issue, and for some reason the adminListGroupsForUser function does not accept the email as username, whereas adminGetUser does.
I worked around that by retrieving the user data with adminGetUser. It returns the user and all its attributes. Retrieve the attribute value with name sub and use it as username for the adminListGroupsForUser call.
Something like this :
const getParams = {
UserPoolId: "" /*put your user pool Id here*/,
Username: "" /* email */
};
cognitoidentityserviceprovider.adminGetUser(getParams, function(err, data) {
if (err) {
console.log(err, err.stack); // an error occurred
return;
}
var sub;
if (data.UserAttributes && data.UserAttributes.length) {
for (var i = 0; i < data.UserAttributes.length; ++i) {
const attr = data.UserAttributes[i];
if (attr.Name === 'sub') {
console.log(attr);
sub = attr.Value
break;
}
}
}
if (!sub)
return;
const groupsParams = {
UserPoolId: event.userPoolId,
Username: sub
};
cognitoidentityserviceprovider.adminListGroupsForUser(groupsParams, function(err, data) {
/* Your code using groups here */
});
});
I am making mock authentication in node js using passport, jwt. I have created successfully api. I am using handlebars for templating.
Suppose when user post credential to login(post), the api should verify the user and return json content. I stuck here how I redirect to another page after redirection.I don't want this in my api.
My code is this:
app.post('/login', (req, res, next) => {
if (req.body.name == '' || req.body.password == '') {
res.status(401).send('Please fill all fields')
} else {
let name = req.body.name;
// let password = req.body.password;
// usually this would be a database call:
let user = users[_.findIndex(users, {
name: name
})];
if (user === undefined) {
res.status(401).send('User not defined')
} else {
if (user.password === req.body.password) {
let payload = {
id: user.id
};
let token = jwt.sign(payload, config.jwtSecret);
res.json({message: "ok", token: token});
} else {
res.status(401).send('Password did not match')
}
}
}
});
My doubt is this from this api How can I redirect to next page if user is authenticated. I can not change code in this api as I am using this api for testing also.
I'm still kinda new to Node.js but I believe to redirect you can use
res.redirect("/pageyouwant");
Maybe in the bracket where the user gets authenticated, you can use the code I wrote above.
Btw, maybe try my code in that place
if (user.password === req.body.password) {
let payload = {
id: user.id
};
let token = jwt.sign(payload, config.jwtSecret);
res.json({message: "ok", token: token});
res.redirect("/pageyouwant"); // <-- This one right here
}
Hope I could helped you.
I am building a project using Deployd for assistance with my API, and dpd-passport for authentication.
I seem to have everything authenticating, with session keys behind handed out and users authentication through Google, but I am having trouble with my redirectURLs, as well as translating the callback page I am returned with.
I have dug into the dpd-passport/index.js file, and I believe this is the relevant information:
var sendResponse = function(ctx, err, config) {
var sessionData = ctx.session.data;
var returnUrl = (ctx.req.cookies && ctx.req.cookies.get('_passportReturnUrl')) || null;
if(returnUrl) {
var redirectURL = url.parse(returnUrl, true);
// only append if not disabled
if(!config.disableReturnParams) {
// delete search so that query is used
delete redirectURL.search;
// make sure query is inited
redirectURL.query = redirectURL.query || {};
if(err) {
redirectURL.query.success = false;
redirectURL.query.error = err;
} else {
// append user + session id to the redirect url
redirectURL.query.success = true;
if(!config.disableSessionId) {
redirectURL.query.sid = sessionData.id;
redirectURL.query.uid = sessionData.uid;
}
}
}
var redirectURLString = '';
try {
redirectURLString = url.format(redirectURL);
} catch(ex) {
console.warn('An error happened while formatting the redirectURL', ex);
}
// redirect the user
ctx.res.setHeader("Location", redirectURLString);
ctx.res.statusCode = 302;
ctx.done(null, 'This page has moved to ' + redirectURLString);
} else {
if(err) {
ctx.res.statusCode = 401;
console.error(err);
return ctx.done('bad credentials');
} else {
ctx.done(err, { path: sessionData.path, id: sessionData.id, uid: sessionData.uid });
}
}
};
After successfully authenticating, I am given a returnUrl of:
http://localhost:3000/auth/google/callback?code=4/l4o-H2F4QKJ5tdKbVbGfWygTGRvhHgr9zrHWImFFKdM#
with an body of:
{"path":"/users","id":"d03c0faccfe41134c193266afef979c5af33adf935aeff45844b0f9473dee4ab1fbd1114240e13ea9a542785da3845cfec984e3a5b8cb188d6c595b6fc39a726","uid":"747f97a9bcfa9811"}
which seems to me like my results are hitting the final else statement in the topmost code block.
If this is true, then my returnUrl is NULL.
Tracing back the returnUrl code in the dpd-passport file, it looks like it should be grabbing this from cookies in the follow snippet:
if(ctx.query.redirectURL && this.config.allowedRedirectURLs) {
try {
this.regEx = this.regEx || new RegExp(this.config.allowedRedirectURLs, 'i');
if(ctx.query.redirectURL.match(this.regEx)) {
// save this info into the users session, so that we can access it later (even if the user was redirected to facebook)
if (ctx.res.cookies) ctx.res.cookies.set('_passportReturnUrl', ctx.query.redirectURL);
} else {
debug(ctx.query.redirectURL, 'did not match', this.config.allowedRedirectURLs);
}
} catch(ex) {
debug('Error parsing RedirectURL Regex!', ex);
}
}
To add to this, I have my allowedRedirectUrls in the config as:
^http://localhost:3000/.*$
I am at a loss and am hoping there is something obvious that I am missing.
I have seen the passport routes and authentication strategies similar to the following, but have been unsuccessful in implementing this into dpd-passport:
app.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: '/login' }),
function(req, res) {
// Successful authentication, redirect home.
res.redirect('/');
});
To add to this all, I am using ui-router/AngularJS.
You have to supply the redirectURL to dpd-passport through the link that starts the oauth procedure:
http://localhost:2403/auth/google?redirectURL=http://localhost