Session Token validation with NodeJS

@jrs40492, why don’t you do this on the cloud code or don’t you have access? I don’t use any client side Parse SDK, purely use the REST api. In cloud code I use these functions:

User.LoginUser = function (req, res) {
  let userQuery = new Parse.Query(Parse.User);
  Parse.User.logIn(req.params.username, req.params.password, { installationId: req.params.installationId }).then(
    user => {
      if (user) {
        // an annoying additional query in order to get the Pointer attributes for a paid customer.
        userQuery
          .first({ sessionToken: user.getSessionToken() })
          .then(
            freshUser => {
              const userObject = mapUserObject(freshUser);
              // the above request is stripping out the sessionToken and I
              // don't know why, it's the same as refresh user below! We'll have
              // to add it in or we get a ton of errors
              userObject.sessionToken = user.getSessionToken();
              res.success(userObject);
            },
            error => {
              res.error(error);
            }
          )
          .catch(error => res.error(error));
      }
    },
    error => {
      res.error(error);
    }
  );
};

function mapUserObject(user) {
  var userObject = {
    id: user.id,
    sessionToken: user.get("sessionToken"),
    username: user.get("username"),
    firstName: user.get("firstName"),
    lastName: user.get("lastName"),
    email: user.get("email"),
    emailVerified: user.get("emailVerified"),
    createdAt: user.createdAt,
    updatedAt: user.updatedAt
  };

  return userObject;
}

If you can’t do this directly with the server then you can maybe call this from the Node intermediate server you seem to be running and return the user object to the client. Notice the returned user object so it can be held in locally. Sorry it’s messy I don’t have much time to respond just now. I will take a look back at this thread a little later to see if I can help anymore.

I just started using Parse, so I haven’t really explored Cloud Code yet. Perhaps I’m missing something but what is the benefit to using Cloud Code over putting the same logic into my API project?

Also, my main issue is every request after the Login. Once the user is verified, I’m trying to find the best way to validate the following requests.

Edit: Just attempted to try my idea above (Query the Session class to get pointer to the User) however, this seems to not be allowed? Gives me an “Invalid session token” error and a quick Google says it’s not allowed due to security reasons.

For me, and what I think is quite normal is for the node server running parse to actually be your API, this additional intermediate layer is an addition complication that’s not required. You can just do everything on the parse server.

If done this way you can easily access the parse user via request.user.getSessionToken(). This session token is passed to your query or save requests to authenticate those calls.

Happy to answer other specifics if you want.

Sorry, I haven’t had time to work on this project the past couple of weeks.

@Simon I’m going to attempt your idea now and see if it works. From what I remember, I didn’t have access to the user object in the request but that may be something I’m doing wrong.

Do you have any code samples by chance?

Edit: Tried using req.user and it is undefined when it comes from the client.

Anyone have any more thoughts on this?

I’ve tried Googling but I can’t find a single implementation of Parse + Node.js that demonstrates user validation after they log in. Every tutorial stops at the login part which I have implemented and is working fine.

Just seems insane that I can’t find any info on this. Either no one uses Parse + Node.js or I’m missing something so obvious that no one has ever asked this question before.

Hi @jrs40492,

Sorry for the delay.

On the client side of the application you are going to have to package up the REST call to include the relevant information, namely the sessionToken.

Take a look at this SO question I asked about 3 years ago. It’s an Angular question, however you should be able to see what is happening with the REST call data.

Essentially ensure you are passing the session token

const headers = {
  'X-Parse-Application-Id': PARSE_APPLICATION_ID,
  'X-Parse-Session-Token': sessionToken;
  'Content-Type': 'application/json'
};

Following login you are going to have to store the session token on the client side in order to pass it with subsequent API calls.

In your example, if you are getting an undefined on the user you need to ensure that they are already logged in and you’re passing the session token. Looking at the example above I am actually doing a cheeky additional query in order to get the full user in the login function as normally the login only returns the session token rather than the full user object.

Post some code if you’re still struggling.

I see that what I’m trying to do is possible by using the REST API for Parse. I will try implementing this and see if it works.

What I don’t understand is, why can’t I just use the Javascript SDK to do the same thing? If it’s possible, there is 0 documentation on it in the developers guide or API.

The methods I am showing you mean that no Client Side Parse Library is needed. However, if you wanted to use a client side Lib then you have 2 choices.

  1. Use the normal login method and you will then have the session token stored in local storage and the Parse JS lib will add this to all requests to the server.

  2. You can use the Parse lib to run custom JS functions in the cloud by running

const params =  { movie: "The Matrix" };
const ratings = await Parse.Cloud.run("averageStars", params);
// ratings should be 4.5
});

See here for the JS calling of cloud functions

Method 1 is what I’m trying to do. But the issue I’m having is, once I have the session token, I can’t figure out how to get the user associated with that token.

I know I could store their User ID in a cookie as well and just use both the Session Token and User ID in each query but I prefer exposing as little as possible to the client side.

If you want to expose as little as possible in the client side then I advise not using the Client JS library and just use the REST API.

But anyway, if you have a session token then you have everything you need on the Cloud. Passing the session token in the method I have show above will give you the request.user object in the cloud code. Unless you have a reason to store the userId on the client side then it’s not needed to make server calls as a logged in user, so long as the session token is valid.

I’m only using the Javascript SDK library on the server side in Node. The only thing that’s ever passed to the client is their token upon login/sign up.

I’ve made some progress using the REST API. Using Postman with the exact same values, I get a valid response, however, I’m using axios in Node.js and it’s giving me “Error: unauthorized”. Here is my code:

const headers = { 'X-Parse-Application-Id': process.env.APP_ID, 'X-Parse-REST-API-Key': process.env.API_KEY, 'X-Parse-Session-Token': req.cookies.token };

axios
  .get(`${process.env.SERVER_URL}/users/me`, headers)
  .then(data => {`

Hang on…is this your stack?

Parse Server 
NodeJS Server (Running the Parse JS SDK?) 
Client Side

I’m pretty sure that the Parse JS SDK is for the client side. It needs to be run in the browser as it stores a load of info into the browsers local storage. I might be wrong, but I’ve never even given it a thought that the ParseJS module could be running on NodeJS.

Maybe someone else can help with that as I’ve never seen that setup before.

Personally I don’t see why there is a Node JS server in the middle of this, can’t you just do everything on the Parse server as it’s just a Node Server it’s self.

To go Client > Node Server > Parse Server > Node Server > Client is a really long way around.

Well right now, nothing is technically split out. Everything is contained in one NodeJS application. The current flow is:

  1. Start NodeJS server
  2. JS SDK initializes Parse Server
  3. Application starts running on port X
  4. User logs in, response contains session token stored in cookie
  5. User navigates to restricted page, passes the session token to GET request
  6. NodeJS Server (using Express Router) receives GET and session token
  7. I validate the session token server side then send the response (this is where I’m stuck)

I would need to see some code or something, can you setup a sandbox environment and pm me the details. I’m happy to help as I have been in this position with Parse and it’s frustrating as hell.

Can you post the code, where you are trying this: I validate the session token server side then send the response (this is where I’m stuck)?

That’s the code I posted above with the axios request. In postman it gives me a successful response but in node with axios, it gives me an 403 error.

I will message you about setting up a sandbox environment. Thank you.

I’m actually facing a similar situation with Nuxt when running on SSR. In Nuxt there’s a certain function (asyncData) that’s called on both server side and client side to initialize the data.

asyncData() {
  Parse.User.current();
}

When running on server side, it’s bound to error because there’s no access to the localStorage. I know I can ditch the Parse SDK and use REST API instead, it’ll be too much of wrapping codes to do.

Or, I could separate the codes depending if it’s running on server or client, but it’s going to duplicated everywhere.

I tried to set the RESTController to read the sessionToken from cookies.

Parse.CoreManager.setRESTController({
  ajax: (...args) => RESTController.ajax.apply(null, args),
  request(...args) {
    const token = getSessionToken(req.headers.cookie)
    return injectSessionToken(token, RESTController).apply(null, args);
  },
})

I was too naive, Parse SDK is a singleton, under race conditions, user A could access user B’s data.

My takeaway was that Parse is not a good choice for SSR, especially the data you’re trying to access is restricted. I guess I’m going to stick with SPA for now.

Instead of using Parse.User static methods, you’d need to send { sessionToken: “” } option in each of your queries.

The problem with sending is not an issue, the main our problem is that, server-side, there is no documented method of verifying a token for validity, e.g. like Firebase has https://firebase.google.com/docs/auth/admin/verify-id-tokens

I have the exact same questions and I can’t really get how a server may verify a client request of authenticity, without a token verification or any sort of checking. Session object is locked (even when master-key is present, why?!), and other methods are obsolete for token validation.

OK, so it took me a lot more time to figure it out, but I got a function that supposed to verify a token and returns the user ID in case it’s logged in or false otherwise. I want to use it server-side, for client-checks:

const verifyToken = token => {

  const query = new Parse.Query(Parse.Object.extend("_Session"));

  return query.equalTo("sessionToken", token).find({ useMasterKey:false, sessionToken:token })
.then( validSessionToken => {
  // There is at least one token
  let userID = false;

  try {
    userID = validSessionToken[0].attributes.user.id;
  }catch(e){}

  return userID;
})
.catch(function(error) {
  // No Suck token in Sessions
 return false;
   });

}

You can test it like:

const goodToken = 'r:6386f8902c5f0e46ea47b5e1f74af27b'
const falseToken = 'r:c799ca30b02a5d134e56850390ed095c';

verifyToken( goodToken ).then( res => {
  console.log('result', res);
});

Hope this will save someone’s time. Cheers!

1 Like

I use to check the session token and return the logged user using this function in the server side:

async function loggedUser(sessionToken) {
  const loggedUserSessionQuery = new Parse.Query(Parse.Session);
  loggedUserSessionQuery.equalTo('sessionToken', sessionToken);
  loggedUserSessionQuery.include('user');

  const loggedUserSession = await loggedUserSessionQuery.first({
    sessionToken
  });

  if (!loggedUserSession) {
    throw new Error('Invalid session token.');
  }

  return loggedUserSession.get('user');
}