How to directly call passport.serializeUser (and avoid calling authenticate altogether)? - passport.js

The question:
In a nutshell - how can I invoke the passport serializeUser function directly ? I am happy with the registered implementaiton I have provided, i just need to be able to invoke it without the indirection of calling authenticate in order to do so.
passport.serializeUser(function (user, done) {
if (user) {
done(null, user);
}
});
As an already authenticated user, inside a Controller, I want to be able to do something like:
var user = req.user; // get the already authenticated user out of the session
user.roles = ['agency']
passport.serializeUser(user); // persist my changes in session storage
deserializeUser will know how to handle the changes.
Background:
Have a working MEAN Stack application using Passport for authentication with a local strategy. Everything works great - the session management is externalized to Redis. However, there is a requirement in the application to be able to "LOGON AS" another type of user to the System. An administrator (user with admin role) can for example log on as one of the users. In effect, I want this to be an illusion - all i will do is alter a few settings about the admin (role information etc and store that in session - the altered data is stored in a stack (js array - memento pattern concept) - and when the admin does a user logoff i pop the original info about the admin back into his session data.
So really what I want is the ability to tweak a few User details about the Admin user already logged in and update their session information with these changes (serialize them) without re-authenticating (apart from anything else I don't have the unencrypted password to call authenticate so that is not an option). The admin is already authenticated. - on deserializeUser the logic will check for enriched data, and if present do the right thing when deserializing the data to keep the impression that the admin is the assumed user identity.
Update:
Ok, so it looks like the behaviour is very different to what I originally expected. As follows:
1). passport.serializeUser appears to only ever be called on authenticate (login). passport.deserializeUser however gets called on every request, and often several times - therefore if putting a DB query in there, expect several queries per request to be possible.
2). req.user is the object that gets updated by passport.deserializeUser. This is DISTINCT from req._passport.session.user !! They are not the same...
3). req._passport.session.user is what updates the session store directly. Hence if you change what req._passport.session.user points at, or alter its attributes, they will internally update the session storage - I can see this behaviour by looking at the session state in Redis DB.
To solve my problem above, i don't actually need to directly call passport.serializeUser. I only need to update the req._passport.session.user with the attribute information i want persisted to session storage. But here is a key point - I believe the attributes that can be updated must match attributes that were already present when passport.serializeUser was originally used to serialize out the initial state. That last point is to be confirmed.
So I can update req._passport.session.user, and it gets persisted to session storage so long as it abides to attributes already stored in the session. Then I can override the behaviour of passport.deserializeUser to use what is passed in as an argument. Again, here is a confusing point - what is passed in as a user argument to passport.deserializeUser is the req._passport.session.user object, and what eventually gets returned by passport.deserializeUser is the req.user object accessible later on.

https://github.com/jaredhanson/passport/blob/master/lib/http/request.js
function here....
...
var self = this;
this._passport.instance.serializeUser(user, this, function(err, obj) {
if (err) { self[property] = null; return done(err); }
self._passport.session.user = obj;
done();
});

Ok, so Passport is really flexible here and accommodates exactly what I needed. Just need to first get up to speed with how it has been implemented internally. Here, I shall call the solution "Parasitic Session Polymorphism" because of the way it uses the existing session, but injects just enough new information to appear to be a different user type etc at runtime. So the solution is basically (with custom code that would need to be swapped out if anyone wishes to use the basic idea):
passport.serializeUser(function (user, done) {
if (user) {
var identityStack = user.identityStack || [];
var parentId = user.parentId || '';
var userStorage = {
_id: user._id,
identityStack: identityStack,
entityId: user.entityId,
entityType: user.entityType,
roles: user.roles,
parentId: parentId
};
done(null, userStorage);
}
});
passport.deserializeUser(function (userStorage, done) {
var id = userStorage._id;
User.findOne({_id: id}).exec(function (err, user) {
if (user) {
if(userStorage.identityStack && userStorage.identityStack.length > 0) {
user.identityStack = userStorage.identityStack;
user.entityId = userStorage.entityId;
user.entityType = userStorage.entityType;
user.roles = userStorage.roles;
user.parentId = userStorage.parentId
}
return done(null, user);
} else {
return done(null, false);
}
});
});
Then in order to actually call the passport.serializeUser from within your own code, you can achieve the required result by currying the http req object. For my implementation I push and pop the added information off a javascript array associated with .prototype as an extension to the Mongoose User Model:
PUSH KEY CODE:
var memento = {
entityId : agency.id,
entityType: 'user',
roles: ['admin', 'user']
};
var user = req.user;
user.pushIdentity(memento);
var doneWrapper = function (req) {
var done = function (err, user) {
if(err) {
console.log('Error occurred serializing user');
return;
}
req._passport.session.user = user;
console.log('user serialized successfully');
return;
};
return done;
};
req._passport.instance.serializeUser(user, doneWrapper(req));
return res.send({success: true, user: req.user});
and POP KEY CODE:
var user = req.user;
user.popIdentity();
var doneWrapper = function (req) {
var done = function (err, user) {
if(err) {
console.log('Error occurred serializing user');
return;
}
req._passport.session.user = user;
console.log('user serialized successfully');
return;
};
return done;
};
req._passport.instance.serializeUser(user, doneWrapper(req));
return res.send({success: true, user: user});
Clearly the code is not yet production ready, move out the console.log statements to a logging strategy and so on but that is the basic concept. The signature to the serializeUser method is defined in the Passport github code base under lib/authenticator.js.
KEY PART:
Authenticator.prototype.serializeUser = function(fn, req, done) {
if (typeof fn === 'function') {
return this._serializers.push(fn);
}
// private implementation that traverses the chain of serializers, attempting
// to serialize a user
var user = fn;
// For backwards compatibility
if (typeof req === 'function') {
done = req;
= undefined;
}
Thanks to Biba for his contributions, really appreciated and assisted in getting to a solution.

Related

How to update the user object in back4app?

I use Node.js and back4app.com
I try to update the user object. Therefore I have read a lot and found this promissing documentation:
let progressId = "xyz";
let userId = "12354"; //aka objectId
const User = new Parse.User();
const query = new Parse.Query(User);
// Finds the user by its ID
query.get(userId).then((user) => {
// Updates the data we want
user.set('progressId', progressId);
// Saves the user with the updated data
user.save()
.then((response) => {
console.log('Updated user', response);
})
.catch((error) => {
console.error('Error while updating user', error);
});
});
But there also is a warning. It states:
The Parse.User class is secured by default, you are not able to invoke save method unless the Parse.User was obtained using an authenticated method, like logIn, signUp or current
How would this look like in code?
My solution
Well, I got it to work. While I figured it out, I have found some small show stoppers. I list it for anyone it may concern.
Thanks #RamosCharles I added the Master Key in Parse._initialize. Only with that .save(null, {useMasterKey: true}) works. Take notice, without null it also won't work.
That's my working code:
let progressId = "xyz";
const User = Parse.Object.extend('User'); //instead of const User = new Parse.User();
const query = new Parse.Query(User);
query.equalTo("objectId", '123xyz');
query.get(userId).then((userObj) => {
// Updates the data we want
userObj.set('progressId', progressId);
// Saves the user with the updated data
userObj.save(null, {useMasterKey: true}).then((response) => {
console.log('Updated user', response);
}).catch((error) => {
console.error('Error while updating user', error);
});
});
Now I'm wondering
why my working code is different from documentation?
how secure is my code? And what is to do to get it more secure?
Yes, their API Reference is very helpful! On this section, there's a "try on JSFiddle" button, have you already seen that?
To update a user object, you must use the Master Key. On the frontend, it's not recommended, and it's better to create a cloud code function and call it on your frontend. However, for test purposes, you can keep using the API Reference, but on JSFiddle, you need to do some changes, here is their sample code, but with the adjustments:
Parse.serverURL = 'https://parseapi.back4app.com';
Parse._initialize('<your-appID-here>', '<your-JSKey-here>', '<Your-MasterKey-here>');
const MyCustomClass = Parse.Object.extend('User');
const query = new Parse.Query(MyCustomClass);
query.equalTo("objectId", "<object-ID-here>");
query.find({useMasterKey: true}).then((results) => {
if (typeof document !== 'undefined') document.write(`ParseObjects found: ${JSON.stringify(results)}`);
console.log('ParseObjects found:', results);
}, (error) => {
if (typeof document !== 'undefined') document.write(`Error while fetching ParseObjects: ${JSON.stringify(error)}`);
console.error('Error while fetching ParseObjects', error);
});
You'll need to insert the "_" before the "initialize" in your "Parse._initialize" and insert the Master Key in your query as I did on the query.find.

How do I authenticate with Node.js + Mongoose?

With the following code I'm not able to authenticate with a MongoDB database, which already has a Users schema and users associated with it and I was wondering how I would make sure that auth returned isAuth?:
exports.auth = function(username, password, session) {
User.findOne({username: username}, function(err, data) {
if (err) {
console.log(err);
}
var isAuth = username === data['username'] & password === data['password'];
if (isAuth) {
session.isAuthenticated = isAuth;
session.user = {username: username};
}
return isAuth;
});
};
First of all, as others have already pointed out in the comments, you shouldn't implement your own authentication logic if you don't know what you're doing. You can use Passport for that.
Now, to the code you provided. There are several problems here.
The first thing that comes to mind is that you use:
var isAuth = username === data['username'] & password === data['password'];
instead of:
var isAuth = username === data['username'] && password === data['password'];
But this is just a typo. Now, to more fundamental stuff.
You cannot return the isAuth variable because who are you going to return it to? If you think that it will get returned to the caller of exports.auth then you're wrong - the exports.auth() will return long before the return isAuth; is ever run.
Also, if yu check for error with if (err) then put the code that should be run in the case of success in the else block o otherwise it will also be run on error with undefined variables that may crash your program.
You need to either add an additional argument to your function which is a callback:
exports.auth = function(username, password, session, callback) {
User.findOne({username: username}, function(err, data) {
if (err) {
console.log(err);
callback(err);
} else {
var isAuth = username === data.username && password === data.password;
if (isAuth) {
session.isAuthenticated = isAuth;
session.user = {username: username};
}
callback(null, isAuth);
}
});
};
or to return a promise from your exports.auth function (but directly from your exports.auth function, not some other callback inside).
Using the above version you can call it with:
auth(username, password, session, function (isAuth) {
// you have your isAuth here
});
The other option would be to use promises. You can see some other answers where I explain the difference between callbacks and promises and how to use them together in more detail, which may be helpful to you in this case:
A detailed explanation on how to use callbacks and promises
Explanation on how to use promises in complex request handlers
An explanation of what a promise really is, on the example of AJAX requests
But first you need to get comfortable with callbacks.
Also, never store the passwords in cleartext in the database. Seriously, use some other solution that works like Passport. I wrote the answer to explain the process of using callbacks, not to endorse the idea of using authentication in that particular way. You have been warned.

connect session save redundant?

How does connect's session work? I'm following this tutorial. I've noticed that even though i've commented out the session.save() call everything still works, that property still gets persisted to the session. Does Express automatically call session.save() every time a response is served up? If that's the case, what is the purpose of the save method?
var BaseController = require('./Base'),
View = require('../views/Base');
module.exports = BaseController.extend({
name: 'Admin',
username: 'dadams',
password: 'dadams',
run: function(req, res, next) {
if(this.authorize(req)) {
req.session.fastdelivery = true;
// req.session.save(function(err) {
var v = new View(res, 'admin');
v.render({
title: 'Administration',
content: 'Welcome to the control panel'
});
// });
} else {
var v = new View(res, 'admin-login');
v.render({
title: 'Please login'
});
}
},
authorize: function(req) {
return (
req.session &&
req.session.fastdelivery &&
req.session.fastdelivery === true
) || (
req.body &&
req.body.username === this.username &&
req.body.password === this.password
);
}
});
Connect’s session handling is automatic. Looking at the code, save gets called automatically on res.end (when your response is sent), so there’s no need to call it separately.
Consider it an implementation detail that’s exposed to you. I can’t think of many reasons why you would use it. Maybe if you are saving to Redis or a database and you want the session to be committed early for some reason, before calling end?

NodeJS express basicAuth - how to pass username to the route function?

I've got a working node app where I need to connect to different DBs based on what user is connecting to the app via basicAuth.
Here's a sample:
// Authenticating function
var sgAuth = express.basicAuth(function(user, pass, callback){
if(config.credentials.clients[user] === undefined) {
callback(null, false);
} else {
callback(null, config.credentials.clients[user].password == pass);
}
});
// This function needs to know what user has authenticated
function putEvents(req, res) {
//How do I know what user authenticated in this request?
var authUser = ???;
var table = getUserTable(authUser);
...
}
app.post('/put', sgAuth, putEvents);
Storing username in sgAuth to some var surely won't work, because there can be many incoming connections from different users, so you can't guarantee that its the same user, right? Can this info be retrieved from the request header somehow?
The basicAuth() middleware will set req.user and req.remoteUser once authorized.
Though, note that the 2nd argument to the callback is expected to be the user, not simply an authorized boolean. But, it can be any truthy value you desire, including the user name.
callback(null, config.credentials.clients[user].password == pass ? user : null);
After that, you should be able to retrieve it with:
var authUser = req.user;
Note that: basicAuth is deprecated
Here the code:
app.use(express.basicAuth(function(user, pass, callback){
if(config.credentials.clients[user] === undefined) {
callback('user not found!!!');
} else {
if(config.credentials.clients[user].password === pass) {
callback(null, config.credentials.clients[user]);
} else {
callback('wrong pass!!!');
}
}
});
app.post('/put', function putEvents(req, res) {
console.log(req.user.name)
res.end();
});

Asynchronous issues with node

I have a function running in a node app that I can't get to work due to my lack of understanding on how to write asynchronous code properly. Below is a function that takes in a profile with emails. I would like to loop through each email and check to see whether that user exists in my database. If they do, I would like to return the callback given and completely exist the function without doing anything else. If the user is not found, I would then like to create a new user based on the information given in the profile, and then return the same callback with the newly created user. As of now, the function works as intended, except that it creates a new user even when a user is already found in my database. (the 'User' variable is defined above and has the 'create' function. Also, I would like to avoid using the 'async' node module if at all possible)
function processProfile(profile, callback) {
var existingUser;
if (profile.emails) {
profile.emails.forEach(function(email) {
console.log("Searching for user with this email:" + email.value);
existingUser = findUserByEmail(email.value);
if (existingUser) {
console.log("Found the existing user");
return callback(null, existingUser);
}
});
if(!existingUser){
console.log("Creating new user");
var newUser = {
id: profile.id,
firstName: profile.name.givenName,
lastName: profile.name.familyName,
email: profile.emails[0].value
};
user.create(newUser, profile.provider, function(err, user) {
if (err) throw err;
return callback(null, user);
});
}
}
}
Is there something wrong with this?
function processProfile(profile, callback) {
var existingUser;
var index = 0;
function processNextEmail() {
if(index >= profile.emails.size()) return; //When we've popped nothing exit
var email = profile.emails[index++];
console.log("Searching for user with this email:" + email.value);
existingUser = findUserByEmail(email.value);
if (existingUser) {
console.log("Found the existing user");
callback(null, existingUser);
processEmail();//recursive call to prcess the next email
} else {
console.log("Creating new user");
var newUser = {
id: profile.id,
firstName: profile.name.givenName,
lastName: profile.name.familyName,
email: profile.emails[0].value
};
user.create(newUser, provider, function(err, user) {
if (err) throw err;
callback(null, user);
processNextEmail();//recursive call to process the next email after creating the user and adding it to the database.
});
}
}
processNextEmail();
}
If you need the recursive logic to not remove the emails, you can do a simple modification involving an indice within the scope of the processProfile() closure.
Also note, the return callback() lines, don't really do anything. Returning from functions that are happening asynchronously is a waste of time. Just call the callback, and then you can call an empty return to skip the rest of the function if you wish, but it is unecessary, unless the return effects the flow of logic.
EDIT: It turns out this is example is too simple to be much more interesting. The code below I used as an example for some people at work who were having trouble grasping async code. The one time I think that it is okay to use sync code in node is for gathering configuration data. Let's pretend that we have configuration stored in a file, that in turn gets the filename from that file, and gathers another layer of configuration data from another file. We can do this two ways, using readFileSyn, or using readFile. The asynchronous version is tricky, because we need to wait for the first step to complete, because we have to grab the filename from the first file, in order to know where the second file is stored. Below is the code for both the sync solution and async solution.
//The synchronous way
function useConfigurationData(configData) {
dosomethinginterestingwith(configData);
}
function getConfigurationData(fileName) {
var fileName2 = fs.readFileSync(fileName);
var configurationData = fs.readFileSync(fileName2);
return configurationData;
}
var fileData = getConfigurationData('someFile');
useConfigurationData(fileData);
//The equivalent async way
function getConfigurationData(fileName, useConfigDataCallBack) {
fs.readFile(fileName, getConfigDataStepTwo);
function getConfigDataStepTwo(err, fileName2) {
fs.readFile(fileName2, getConfigDataStepThree);
}
function getConfigDataStepThree(err, fileData) {
useConfigDataCallBack(fileData);
}
}
getConfigurationData('someFile', useConfigurationData);
Notice that the callback we supply to getConfigurationData is the last step. We could also just rely on the globally defined getConfigurationData function, but passing it in as a callback is better style.
What I like about this syntax, is the code withing the second getConfigurationData function reads in order, very synchronously. But if you follow the flow of logic, it is all being run async. It's easy to read and follows nodes async I/O model. IN the case of configuration data, I think the synchronous option is acceptable, but this is still a good demo of how to get synchronous behavior and syntax(ish) from asynchronous callbacks.

Resources