passport.authorize() clearing req.user with multiple (same) strategies - node.js

I need two instances of a passport local strategy ("localA" and "localB"), one instance of this authenticates against a collection "colA" in "DbA" and is used in one route sequence (Route A), the other instance authenticates against another collection (ColB) in "DbB" and is used in a second route sequence (Route B).
In both cases, access to "req.user" is needed. In the first route, "req.user" has its expected defined value, however, in the second route, "req.user" is undefined. Here is an extract of what I believe to be the relevant code:
const userA = DbA.model(`colA`, userASchema);
passport.use(`localA`, new passportLocalStrategy({usernameField: `email`, passwordField: `password`}, userA.authenticate()));
passport.serializeUser(userA.serializeUser());
passport.deserializeUser(userA.deserializeUser());
const userB = DbB.model(`colB`, userBSchema);
passport.use(`localB`, new passportLocalStrategy({usernameField: `email`, passwordField: `password`}, userB.authenticate()));
passport.serializeUser(userB.serializeUser());
passport.deserializeUser(userB.deserializeUser());
//Route A
app.post('/routeA', passport.authenticate(`localA`), (req, res) => {
res.redirect(`/routeAA`);
});
app.get('/routeAA', function (req, res) {
res.render('routeA.ejs');
});
//Route B
app.post('/routeB', passport.authenticate(`localB`), (req, res) => {
res.redirect(`/routeBB`);
});
app.get('/routeBB', function (req, res) {
res.render('routeB.ejs');
});
It appears that this is not a new issue. Here are some related posts:
https://github.com/jaredhanson/passport/issues/803
Passport.js multiple local strategies and req.user
In post 803 user #nathan6am, states ....
I ran into the same problem, it's a bit of a hacky solution but I got
around it by using req.session?.passport?.user and deserializing the
user manually instead of using req.user in the callback.
I'm still struggling to understand how to manually de-serialize so as to force req.user to re-acquire correct values, but I did confirm that the contents of "req.session.passport.user" (for my schema) is the user's email address, so I saved that in a session variable, "req.session.email". My plan was then to write some middleware (in the next route) that would search my DB, using the contents of req.session.email, then use that DB record to extract the data that I would subsequently pass onto my rendered ejs file. It would have looked something like this:
//Route B
app.post('/routeB', passport.authenticate(`localB`), (req, res) => {
req.session.email = req.session.passport.user;
res.redirect(`/routeBB`);
});
app.get('/routeBB', hack, function (req, res) {
res.render('routeB.ejs' {key1: value1, key2: value2 });
});
function hack(req, res, next) {
// find user in DB using req.session.email
// extract need data from DB
// build object comprised of needed data
// key1: value1
// key2: value2
return next();
}
But then I realized that I have other middleware, for other routes, that rely on req.user for authorization (req.user.role=some role) ... so having req.user as undefined isn't something that can work. Is there anyone who can add some color to #nathan6am's post?
Thank you.
Tim.

Related

make a final authentication middleware to return data and validate user?

hello there I'm trying to implement a method to handle returning data in my express project, I want to pass data in all my controllers to a final middleware which its job is to check whether the user has the authority to operate on the document and then return the data ,
req.document = order;
req.validateFields = [
order.senderId._id.toString(),
order.recieverId._id.toString(),
order.driverId._id.toString(),
];
next();
then I handle it in my final middleware like this
app.use(async (req, res, next) => {
const document = req.document;
if (!req.validateFields.includes(req.user._id.toString()))
console.log("not authorized");
await document.save();
res.status(200).json({
document,
});
});
first of all is this a bad practice ? since I save my document after passing the user validations so the data manipulations that have been done in the controllers will not be saved if the user was not authenticated , and then how to handle the other document methods like findByIdandDelete, findByIdAndUpdate which will save the document right away?
thanks.

I am wondering how to communicate between controllers

I want to invoke the user creation API after confirming the token internally in the server when I click the authentication link in the e-mail to implement the membership method using e-mail authentication.
//emailcontroller.js
router.get('/register/token', function(req, res) {
// check token
if(check(req.params.token)) {
request('http://localhost:8080/api/user', function(data) {
});
}
});
//usercontroller.js
router.post('/api/user', function(req, res) {
var user = new User();
user.userId = req.body.userId;
user.userPw = req.body.userPw;
user.save();
});
I want to invoke the user creation API after confirming the token internally in the server when I click the authentication link in email in order to implement membership method using email authentication.
As mentioned above, the email controller and the user controller are divided and each is routed. I want to modularize the code so that I want to call the existing user creation API to use it for general purpose rather than creating and exports common functions for a specific controller.
/*I do not want to implement it this way.*/
//emailController.js
router.get('/register/token', function(req, res) {
// check token
if(check(req.params.token)) {
userContoller.createUserFromEmail(userId, userPw);
}
});
//userController.js
exports.createUserFromEmail = function(userId, userPw) {
var user = new User();
user.userId = userId;
user.userPw = userPw;
user.save();
}
However, I have never seen communication between controllers in many examples. So I do not know if the way I thought was right. Rather, I think the cost of calling api internally on the server might be higher.
I want to know the correct pattern for communication between controllers. Please bear in mind that there is only a stack overflow when raising a question.
You got the right idea about exposing your API functionality as stand-alone functions (or classes). To avoid duplication, just call your internal methods from within your route handlers. So in your example:
router.post('/api/user', function(req, res) {
createUserFromEmail(req.body.userId, req.body.userPw);
});
In my own projects, I use classes to create my API. First I define a class with just the functionality and then I expose the methods in the route handlers:
export default class User {
read() {
}
create() {
}
update() {
}
delete() {
}
}
const user = new User();
router.get('/user/:id', (req, res) => user.read(req.params.id));
router.post('/user', (req, res) => user.create(req.body.data));
router.put('/user/:id', (req, res) => user.update(req.params.id, req.body.data));
router.delete('/user/:id', (req, res) => user.delete(req.params.id));
This should give you an idea of what you can do. You can write custom middleware and class decorators to reduce the boilerplate.
From your question what I understood:
You want to validate internally the token passed in query parameter, before doing anything else in the user controller.
I believe you are using express, and with express comes middlewares.
From docs:
Middleware functions are functions that have access to the request object (req), the response object (res), and the next middleware function in the application’s request-response cycle. The next middleware function is commonly denoted by a variable named next.
What I usually do and a generally good practice is, pass the token in create user api and attach to email body.
for example:
api/user?token=somerandomstringloremispum
Route file:
router.post('/user', validateEmail, userController.create);
here validateEmail is a middleware function and will be invoked before create method of userController.
Now in your validateToken method, you can simply validate your token like:
function validateEmail (req, res, next) {
if(!valid(req.query.token)) {
//return with appropriate invalid token msg using res.json() or however you like
}
// if validated call `next middleware` like so:
next();
// this will allow `create` method of userController be invoked
}

check if user logged in by passport Strategy (different user types)

I have two user collections in my db and I want to make different login types for every one, so I have made two strategy on passport for my site ('local-user' & 'local-manager').
My question is how to check logged in user type (by used strategy) in my app?
In this code, passport just checks user auth but I want to check by strategy. (eg: if user logged in by 'local-manager', then show the page)
function isLoggedIn(req, res, next){
if (req.isAuthenticated()) {
next();
return;
}
res.redirect('/login');
}
It's better you use role mapping for this.
Anyway for now you can use this concept :
var passport = require('passport')
, LocalStrategy = require('passport-local').Strategy;
passport.use(new LocalStrategy(
{passReqToCallback: true},
function(req, username, password, done) {
req.usedStrategy = 'local-user';
//do auth stuff
});
}
));
And use like this :
function isLoggedIn(req, res, next){
if (req.isAuthenticated() && req.usedStrategy === 'local-user') {
next();
return;
}
res.redirect('/login');
}
Also you can use session if you enable it in passport.
It must be said (and has been in other answers/comments) that you should really look again at your modelling of the domain. User objects can be really simple (just login information) and the rest can be broken out into other models/schemas.
Anyway on to the answer to your original question:
You can switch on the user type. Passport doesn't reach too far into the rest of your application. The log in strategies are not known about outside of the actual log in section.
You can handle that as middleware and add some extra information to the request object by checking for a unique property in one of the models:
function(request, response, next){
request.isManager = !!(request.user && request.user['unique_prop']);
next();
}
Place this after the auth middleware. Then in your route you can switch based on request.isManager. Also encapsulating this in middleware will abstract it from the user model and allow you to refactor it in the background.
Another alternative would be to add the function as a static/method/virtual (depending on the implementation) to the schema if you're using mongoose.
Hope this helps 👍 If you have further questions feel free to add comments and I can amend the answer. 🤔
I know this question is old, but I just had the same problem, and I managed to find a way to at least know what kind of account the user is. As someone else said, I don't think there is a way to see what strategy a user used to log in, but if you have a field in your user database to show what kind of account they are (e.g.: admin, client, etc.), then you can pass this information along in the req.user variable when you serialize the user:
passport.serializeUser(function(user, cb) {
process.nextTick(function() {
return cb(null, {
id: user.user_id,
username: user.user_email,
type: user.user_type
});
});
});
passport.deserializeUser(function(user, cb) {
process.nextTick(function() {
return cb(null, user);
});
});
In the example above, I'm using an SQL database, and one of the columns is called user_type. Once the user is deserialized, passport passes on the fields you ask for in passport.serialize() to req.user. So, if you wanted to know what type of account is currently logged in, you could say something like:
console.log("This user is a(n) " + req.user.type);
Or, something more realistic, if you're using Express:
app.get("/adminsOnly", (req, res) {
if (req.isAuthenticated() { // returns true if a user successfully logged in
if (req.user.type === "admin") { //check if user is an admin
res.render("adminsOnly.ejs"); //render admin section of website if true
} else {
res.redirect("/adminsLogin"); //redirected somewhere else because user is not an admin
}
} else {
res.redirect("/login"); //req.isAuthenticated() returns false, so user is redirected to the login page
}
});
I'm pretty new to coding in general, so I'm sure there are better ways to tackle this question, but this is a way to work around the problem of not being able to pinpoint which strategy the user logged in with.

Express rendering in/after post

Web app routing novice here. I've got a relatively simple app working based on Node/Express.
The main index of the app is a list of user names, and IDs.
localhost:242
Every user ID is a link to a page with a form to enter additional metadata about that particular user.
localhost:242/user/1398
Everything is working correctly. When I enter some metadata about the user, and submit the form, a POST route is executed, and then I'm redirected back to the original page I was on. Instead of using a redirect, I'd like to be able to re-render that same page, so I can pass some confirmation messages indicating what was just changed.
Here's a simplified version of my code.
// Module imports
var express = require('express');
var validator = require('express-validator');
var router = express.Router();
router.get('/', function(req, res, next) {
db.dataTalk(queryUsers, null, config.connection, function(err, result) {
var listUsers = result;
res.render('index', {
// Index with list of users
title: 'Page Title',
listUsers: listUsers
});
});
});
// GET /user/:id
router.get('/user/:id', function(req, res, next) {
db.dataTalk(queryUserDeets, [req.params.id], config.connection, function(err, result) {
// Details for a single user
var userDetails = result;
res.render('user', {
title: req.params.id,
userDetails: userDetails
});
});
});
// POST /user-update
router.post('/user-update', function(req, res) {
// Here goes a lot of logic to validate the form contents, and update the appropriate databases
// Redirect back to the user page, which should display the updated metadata
res.redirect('/user/' + req.body.userInitID);
});
module.exports = router;
Extract a helper function you can call from both places. Here's one that sticks very close to your original code.
function renderUserPage (userId, res) {
db.dataTalk(queryUserDeets, [userId], config.connection, function(err, result) {
// Details for a single user
var userDetails = result;
res.render('user', {
title: userId,
userDetails: userDetails
});
});
});
// GET /user/:id
router.get('/user/:id', function (req, res) {
renderUserPage(req.params.id, res)
});
// POST /user-update
router.post('/user-update', function(req, res) {
// Here goes a lot of logic to validate the form contents, and update the appropriate databases
// Redirect back to the user page, which should display the updated metadata
renderUserPage(req.body.userInitID, res);
});
Aside: You are ignoring errors from database calls. If you don't at the very least log something for each and every error passed to an async callback, you are going to be blind to problems that would otherwise be straightforward to debug.

Passport update session isn't persisting

I'm trying to update my user session with a new name, an easy task I thought.
I'm a logged in user and in hitting a 'update' route and I've defined my own middleware to update the session:
module.exports = function(req, res, next) {
console.log(req.user);
req.login(req.body.user, function(err) {
if (err) return next(new Error('Error updating user profile'));
console.log('USER UPDATED *******', req.user);
next();
});
};
It took a bit of time to dig out the above code which should simply update the Passport session object. It correctly logs the previous session, and then the updated session but when I navigate to a new page after the inital response the user object is entirely lost and just returns {}.
Any ideas?
source
To log in a user and persist it into session passport uses a serialize function which typically stores user.id as a cookie and a deserialize function which retrieves that cookie and does a User.findById database call to find the associated user, and if found one, that's the user object that gets stored in req.user.
req.login passes whatever you pass it as the first argument directly to passport.serialize, which otherwise would've typically come from a strategy, which itself would've retrieved the user object from a database call, or created one.
So when you use req.login you need to pass it the user object that passport.serialize actually would've received, so that it could store the id in a cookie.
In your case, you were doing req.login(req.body.user, ... and since req.body.user comes from a POST Form variable it must not have the id that passport.serialize would've stored in the cookie.
You should instead use the new values from req.body.user and update req.user itself, and then do req.login(req.user, ...
var _ = require('lodash');
module.exports = function(req, res, next) {
//using lodash merge the updated user into the cached user
_.merge(req.user, req.body.user);
req.login(req.user, function(err) {
if (err) return next(new Error('Error updating user profile'));
console.log('USER UPDATED *******', req.user);
next();
});
};

Resources