Working with Parse JavaScript SDK in SailsJs

Recently I have been trying out Parse JavaScript SDK in the frontend. Using the SDK has been a breeze for prototyping, especially when I don't have to take care of backend code and make an extra effort to do database queries. However, it's a necessary evil to write extra lines of code to make each REST call to Parse explicitly. So after the code base starts to grow, the code on the frontend also starts to look really repetitive, so I started to thinking about a simpler way to refactor the code to accomplish these few things:

  • simplify frontend code
    • removing repetitive code
    • extract out the complicated parse logic (innerQuery etc..)
  • filter/map the returned object
  • build a good foundation for future scaling

The first two could be done by refactoring current controllers into services, but the third really pushed me to think about moving the parse code to NodeJs, which is what I ended up doing.

However, I immediately ran into a challenge - multi-user login. Normally, Parse is used on the client side, so whenever a user logs in from the client side, Parse initializes around the user and makes REST calls with that particular user. On the other hand, Nodejs is running as one instance on the server, so the Parse object actually remembers the current user each time someone attempts to log in. Ex: just before the second user logs in, the second user can actually access the information from the first user since NodeJs is still remembering the first user.

The solution to this problem is pretty simple, but I didn't find a lot of information online. Here is what I ended up doing:

  • On client-side side, GET /login and store parse session token on the front-end
  • On client-side side, for every request, attach session token in the request header.
  • On back-end side, check every request's session token from header.
  • On back-end side, if the session token is valid, log the user in with Parse.

Here are the steps:

Step 0: setup Parse inside SailsJs

Inside config/globals.js, expose Parse as a global variable:

module.exports.globals = {
    Parse: true
}

Inside config/bootstrap.js, initialize Parse:

module.exports.bootstrap = function(cb) {
  var APP_ID = <Parse-Application-Id>;
  var REST_API_KEY = <Parse-Rest-Api-Key>;

  Parse = require('parse').Parse;
  Parse.initialize(APP_ID, REST_API_KEY);
  cb();
};

Generate User's api:

sails generate api users 
Step 1: login user

Front-end side, send GET request to /Users/login

Restangular.all("Users").customGET("login", form)
  .then(function(user) {
        ...
    })

And inside SailsJs, add a login function inside api/controllers/Users.js to log the user return the JSON object of user as well as the user's Parse session token.

module.exports = {
  login: function (req, res) {
      Parse.User.logIn(req.query.username, req.query.password, {
          success: function(user) {
              if (!user) return res.send(user);
              req.session["x-parse-session-token"] = user._sessionToken;
              var tmp_user = user.toJSON();
              tmp_user.id = user.id;
              tmp_user._sessionToken = user._sessionToken;
              res.send(tmp_user);
          },
          error: function(user, error) {
              res.send(error, 401);
          }
   });
  }
}
Step 2

On the Front-end side, send a request with the previously stored header $scope.sessionHeader, for example, let's utilize Parse's method to get the current user, and to get it with angular:

Restangular.all("Users").customGET("current", {id:  $scope.user.id}, $scope.sessionHeader)
.then(function(currentUser) {
    $scope.currentUser
})

Inside api/policies/sessionAuth.js, we need to first verify with Parse's api to check if the user's session token is valid, and if it is valid, we continue the request. Otherwise we returned forbidden error.

module.exports = function(req, res, next) {
    Parse.User.become(req.session["x-parse-session-token"])
    .then(function(result){
        next();
    }, function(error){
        res.forbidden(error);
    });
};

To enforce sessionAuth to be acted on each request, specify in config/policies:

module.exports.policies = {
    '*': 'sessionAuth',

    UsersController: {
        login : true, 
        logout : true,
        current : true
    }
}

Doing this makes sure all requests will go through the sessionAuth inside api/policies/sessionAuth.js, but UsersController's function login and logout are exempted.

And to finish getting the current user with Parse, add function current inside UsersController:

current: function(req, res) {
    if (req.session["x-parse-session-token"]) {
        Parse.User.become(req.session["x-parse-session-token"])
            .then(function(user){
                if (!user) return res.send(user);
                var tmp_user = user.toJSON();
                tmp_user.id = user.id;
                res.send(tmp_user);
            }, function(error){
                res.send(null);
            });
    } else {
        res.send(null);
    }
}

And it's done! But it's really slow!

Javascript and User Authentication for the REST API

Validating Session Tokens / Retrieving Current User

Log in by using sessionToken in Javascript

become() session token and Google signup / login

comments powered by Disqus