My mobile hybrid app is connected to an expressJS server that acts as the backend for proxying requests to parse.com through the REST API. Additionally, I have implemented user authentication using express with a Single Sign-On (SSO) provider. Although I followed a tutorial similar to this one, I made modifications to suit my specific setup by not utilizing CloudCode and authenticating without GitHub. I am also taking advantage of the new Revokable Sessions feature introduced earlier this year (around March 2015?). Essentially, both the tutorial and my authentication method involve the following steps on a remote backend (ExpressJS / CloudCode):
Login As User to Obtain Session Token
If the username does not already exist, create a new user and proceed
Generate a random password for the user (updating the user's password using the masterKey)
Log in as the user with the new password to generate the Parse sessionToken
Send back the sessionToken to the client app
While this process works well and is commonly used for third-party authentication providers, there is an issue where each time a user logs in, a new sessionToken is created, essentially invalidating the old token. This results in users having to re-login if switching between devices or platforms. The blog post about enhanced sessions suggests that revokable sessions offer unique sessions per device; however, this functionality seems to be ineffective when users log in via the REST API from my express backend. It is possible that unique sessions only function properly when the app communicates directly with Parse, allowing for the passing of an installationId to differentiate between devices.
Below is the code snippet for my authentication, using the parse object from this npm parse library:
upsertUser function:
/**
* This function checks if the user has logged in before.
* If found, update their password to 'become' them and return
* the user's Parse session token. If not found, createNewUser
*/
exports.upsertUser = function(ssoData, deferred) {
var userCreated = (typeof deferred != "undefined") ? true : false;
var deferred = deferred || q.defer();
var query = {
where: {username: ssoData.uid.trim()}
};
// set masterKey
parse.masterKey = parseConfig.MASTER_KEY;
// find existing user by username
parse.getUsers( query, function (err, res, body, success) {
if ( body.length ) {
var userId = body[0].objectId;
var username = body[0].username;
var password = new Buffer(24);
_.times(24, function (i) {
password.set(i, _.random(0, 255));
});
password = password.toString('base64');
parse.updateUser(userId, {password: password}, function (err, res, body, success) {
if ( typeof body.updatedAt != 'undefined' ) {
console.log('user update at: ', body.updatedAt);
parse.loginUser(username, password, function (err, res, body, success) {
deferred.resolve( body.sessionToken );
});
}
});
} else if ( userCreated === false ) {
console.log('object not found, creating new user');
self.createNewUser(ssoData, deferred);
} else {
deferred.resolve();
}
});
return deferred.promise;
}
createNewUser function:
/**
* This function creates a Parse User with a random login and password, and
* once completed, calls upsertUser.
*/
exports.createNewUser = function(ssoData, deferred) {
// Generate a random username and password.
var password = new Buffer(24);
_.times(24, function(i) {
password.set(i, _.random(0, 255));
});
var newUser = {
username: ssoData.uid,
password: password.toString('base64'),
};
// Sign up the new User
parse.createUser(newUser, function(err, res, body, success) {
if (err) {
console.log('new parse user err', err)
}
if (typeof body.sessionToken != "undefined") {
self.upsertUser(ssoData, deferred);
} else {
deferred.resolve();
}
});
}
Any suggestions on how to prevent sessionTokens from being invalidated upon subsequent logins?