Authenticating a user in server-side routes

I have an app where I need users to be able to download dynamically-generated files from the server. I also need these files to have authorization rules so only the appropriate users can download them. I’ve looked through a number of topics about this (mostly from a few years ago) on the forums an elsewhere, and I wondered if there had been any progress toward making authorization possible natively on server-side routes using WebApp.connectHandlers.

The solutions in https://forums.meteor.com/t/how-to-get-current-user-inside-webapp-connecthandlers/27165/7 don’t seem to work anymore – Meteor can’t find the recommended packages once I install them and Meteor can’t find _hashLoginToken() on the Accounts module when I import it. So I’m out of ideas.

If you’re going to provide server-side routing, which I think is critical in many applications, I think there should be a simple way to handle authentication. Let me know if I’m missing something.

In case anyone else comes across this, you can get the logged in user by passing the user’s loginToken as the access_token query string parameter and then using a slightly rewritten version of what was suggested in the link I posed above to get the user’s full record:

function getUserId(req) {
    let token;
    if (req.headers && req.headers.authorization) {
      var parts = req.headers.authorization.split(" ");
    
      if (parts.length === 2) {
        var scheme = parts[0];
        var credentials = parts[1];
    
        if (/^Bearer$/i.test(scheme)) {
          token =credentials;
        }
      }
    }
    if (!token && req.query && req.query.access_token) {
      token = req.query.access_token;
    }
    if (!token) {
      return null;
    }

    var user = Meteor.users.findOne({
      "services.resume.loginTokens.hashedToken": token,
    });

    if (!user) {
      return null;
    }
    return user._id;
}
function hashLoginToken(loginToken) {
  const crypto = require('crypto');
  const hash = crypto.createHash('sha256');
  hash.update(loginToken);
  return hash.digest('base64');
}

It’s not ideal but it gets the job done. I don’t know if it’s 100% secure (definitely safer in https) but it gets the job done.

It would still be nice to have this functionality built into the framework.

Thanks for sharing, I’m looking for this as well. I can’t help but I’m definitely interested as well to know if this is a validated and secure way of doing this.

I have been trying to figure out how Meteor accounts-base itself re-constructs the Meteor.user from the meteor_login_token Cookie, but have not been successful so far. Look for the defaultResumeLoginHandler in accounts-base.

OK, after realizing that I had been working with a stale cookie all day, I was able to make the server-side rest login work.

By default Meteor uses localStorage to store session information and only falls back to cookies when localStorage is not available.

The current loginToken can be retrieved via Meteor._localStorage.getItem('Meteor.loginToken')

In my test application I simply set a cookie called meteor_login_token to the value of the Meteor.loginToken

In my rest function I can then use that token to get the userId

  WebApp.connectHandlers.use('/api/json/hello', (req, res, next) => {
    const json = {
      loginToken: null,
      hashedToken: null,
      userId: null,
    }
    console.log(`request method ${req.method}`, req.cookies);
    json.loginToken = req.cookies?.meteor_login_token;
    // the following code has been copied from accounts-base
    // get the user
    if (Meteor.users) {
      // check to make sure, we've the loginToken,
      if (json.loginToken) {
        json.hashedToken = Accounts._hashLoginToken(json.loginToken)
        var query = { 'services.resume.loginTokens.hashedToken': json.hashedToken }
        var options = { fields: { _id: 1 } }
        var user = Meteor.users.findOne(query, options);
        if (user) {
          json.userId = user._id
        } else {
          json.message = `/api/json/hello no user for ${json.loginToken}`;
        }
      } else {
        json.message = `/api/json/hello meteor-login-token undefined`;
      }
    } else {
      json.message = `/api/json/hello Meteor.users does not exist`;
    }

    res.writeHead(200);
    res.end(`Hello world from: ${Meteor.release}` + EJSON.stringify(json, { indent: 4 }));
  });

3 Likes