Loopback: How to get current authenticated user

Created on 18 Sep 2014  ยท  75Comments  ยท  Source: strongloop/loopback

In requeest I have valid access token so user available in beforeRemote as part of ctx

But how can I get userId from inside of function. For example:

model.js

var loopback = require('loopback');

module.exports = function(someModel) {

someModel.getUserId = function(callback) {
    // ??? How ???
    var userId = loopback.request.getUserId();
};

spent 3 hours on it and can't find easy solution... Basically how can I get user ID from any place in the code?

Most helpful comment

Elegant way of getting userId from acccessToken by request object in remote method:

  MyModel.register = function(req, param, cb) {
    var userId = req.accessToken.userId;

    console.log(userId); 
    cb(null, ...);
  };

  MyModel.remoteMethod(
    'register',
    {
      accepts: [
        { arg: 'req', type: 'object', http: {source: 'req'} }, // <----
        { arg: 'param', type: 'string', required: true },
      ]
    }

All 75 comments

In the beforeRemote, you can find the userId in ctx.res.token.userId
Not sure this is the best solution or not.

Yes I know it.

But what if I need it inside of function? not in hook? Or how can I pass it to the function without using global variables?

Thanks

I am afraid there is no way how to pass the current user to the remoted method. We are working on a solution that will make this possible - see https://github.com/strongloop/loopback/pull/337

A hacky workaround: use the new header argument source (see https://github.com/strongloop/strong-remoting/pull/104) to get the access-token from the request header, then look up the userId by querying access tokens.

For anyone else having this problem, here is my implementation of the "hacky workaround" suggested by @bajtos . I put this in my server.js file.

// Retrieve the currently authenticated user
app.use(function (req, res, next) {
  // First, get the access token, either from the url or from the headers
  var tokenId = false;
  if (req.query && req.query.access_token) {
    tokenId = req.query.access_token;
  }
  // @TODO - not sure if this is correct since I'm currently not using headers
  // to pass the access token
  else if (req.headers && req.headers.access_token) {
    // tokenId = req.headers.access_token
  }

  // Now, if there is a tokenId, use it to look up the currently authenticated
  // user and attach it to the app
  app.currentUser = false;
  if (tokenId) {
    var UserModel = app.models.User;

    // Logic borrowed from user.js -> User.logout()
    UserModel.relations.accessTokens.modelTo.findById(tokenId, function(err, accessToken) {
      if (err) return next(err);
      if ( ! accessToken) return next(new Error('could not find accessToken'));

      // Look up the user associated with the accessToken
      UserModel.findById(accessToken.userId, function (err, user) {
        if (err) return next(err);
        if ( ! user) return next(new Error('could not find a valid user'));

        app.currentUser = user;
        next();
      });

    });
  }

  // If no tokenId was found, continue without waiting
  else {
    next();
  }
});

This is the workaround I used on a custom route /whoami

    router.get('/whoami', function (req, res) {
        var app = require('../server');
        var AccessToken = app.models.AccessToken;
        AccessToken.findForRequest(req, {}, function (aux, accesstoken) {
            console.log(aux, accesstoken);
            if (accesstoken == undefined) {
                res.status(401);
                res.send({
                    'Error': 'Unauthorized',
                    'Message': 'You need to be authenticated to access this endpoint'
                });
            } else {
                var UserModel = app.models.user;
                UserModel.findById(accesstoken.userId, function (err, user) {
                    console.log(user);
                                       res.status(200);
                                       res.send();
                });
            }
        });
    });

this route is inside /server/boot/root.js

@amenadiel - thanks for posting your solution! I didn't know about that AccessToken method findForRequest. Here is my solution updated to use that method, which solves my original TODO:

// Retrieve the currently authenticated user
app.use(function (req, res, next) {
  app.currentUser = null;

  // Retrieve the access token used in this request
  var AccessTokenModel = app.models.AccessToken;
  AccessTokenModel.findForRequest(req, {}, function (err, token) {
    if (err) return next(err);
    if ( ! token) return next(); // No need to throw an error here

    // Logic borrowed from user.js -> User.logout() to get access token object
    var UserModel = app.models.User;
    UserModel.relations.accessTokens.modelTo.findById(token.id, function(err, accessToken) {
      if (err) return next(err);
      if ( ! accessToken) return next(new Error('could not find the given token'));

      // Look up the user associated with the access token
      UserModel.findById(accessToken.userId, function (err, user) {
        if (err) return next(err);
        if ( ! user) return next(new Error('could not find a valid user with the given token'));

        app.currentUser = user;
        next();
      });

    });
  });

});

I don't recommend attaching the user to the app object, it will stop working once you have more than one request being handled in parallel.

Setting that aside, here is a simpler solution:

app.use(loopback.token());
app.use(function(req, res, next) {
  app.currentUser = null;
  if (!req.accessToken) return next();
  req.accessToken.user(function(err, user) {
    if (err) return next(err);
    app.currentUser = user;
    next();
  });
});

But as I said, don't do that. It creates a hard to debug bug where Request A sees app.currentUser filled by Request B in case these requests arrive at the server (almost) in parallel.

@bajtos - wow, thank you for taking the time to point this out. To take your advice and avoid this potential (serious) problem, I have attached the currentUser to the request object rather than the app object so that it is specific to the remote context, as follows:

app.use(loopback.token());
app.use(function (req, res, next) {
  if ( ! req.accessToken) return next();
  app.models.User.findById(req.accessToken.userId, function(err, user) {
    if (err) return next(err);
    if ( ! user) return next(new Error('No user with this access token was found.'));
    req.currentUser = user;
  });
});

I tried first using req.accessToken.user(), but user kept being returned as null.

Now, I am able to access this currentUser in all of my beforeRemote hooks via ctx.req.currentUser. I think this should avoid any issues of user B's credentials being available in user A's request, as each request is a different object, thus each request will have the correct user's information.

@mymainmanbrown - this helped a lot, thank you! I am quite satisfied with the end result of this thread.

Small note: your sample above is missing a final next() call after the currentUser is assigned.

@bajtos - thanks for the commit!

@doublemarked - thanks for the catch. Since implementing this in my own project, I've discovered a "more proper" way of doing this, though the steps and end result are the same. Looking through the expressjs docs, I came across the entry on res.locals, which appears to be intended to store session/state data for the current request. Thus, without further ado, here is the code I'm using now, with the next() call:

app.use(loopback.token());
app.use(function (req, res, next) {
  if ( ! req.accessToken) return next();
  app.models.User.findById(req.accessToken.userId, function(err, user) {
    if (err) return next(err);
    if ( ! user) return next(new Error('No user with this access token was found.'));
    res.locals.currentUser = user;
    next();
  });
});

This is parallel-safe and can be access anywhere you have access to ctx or res.

And if you want to use the new context functionality, you can store the user in the loopback context too:

app.use(loopback.context());
app.use(loopback.token());
app.use(function (req, res, next) {
  if (!req.accessToken) return next();
  app.models.User.findById(req.accessToken.userId, function(err, user) {
    if (err) return next(err);
    if (!user) return next(new Error('No user with this access token was found.'));
    res.locals.currentUser = user;
    var loopbackContext = loopback.getCurrentContext();
    if (loopbackContext) loopbackContext.set('currentUser', user);
    next();
  });
});

// anywhere in the app
var ctx = loopback.getCurrentContext();
var currentUser = ctx && ctx.get('currentUser');

Beautiful, thank you guys.

@bajtos What is this new context functionality and where is it documented? It is new as of what version?

What is this new context functionality and where is it documented? It is new as of what version?

It was not released yet, we are expecting to release it early next week. You can give it a try by installing loopback from github master (npm install strongloop/loopback). The documentation is in progress, it's not done yet.

Until we have real docs, you can learn about the feature from the pull request - see #337.

Ok, thank you very much! I enjoy your enthusiasm for upcoming features :)

@mymainmanbrown - How would you access res.locals.currentUser inside a remote method?

@pulkitsinghal - You need to have a res handle, which you can pass in as a parameter when you declare the remote method. However, since the recent release we've swapped to using the loopback.getCurrentContext() method that @bajtos recommended and which does not require a res handle.

@doublemarked - But I just don't know how to get at the loopback variable inside a common/models/model.js file, can you suggest?

It's a handle for the loopback module,

var loopback = require('loopback');

@doublemarked - Thank You ... strange I could have sworn that failed with an earlier version of loopback but now it works inside model files after upgrade to 2.8.5 ... may I ask a follow up?
For console.log(loopback.getCurrentContext().accessToken()); it throws "message": "Object #<Namespace> has no method 'accessToken'", ... what could be going on?

Well, indeed I don't think accessToken is a valid function within the context instance. Perhaps you mean something like the following?

console.log(loopback.getCurrentContext().get('accessToken'));

Can you compare carefully your code with the examples earlier in this thread? Our implementation is almost exactly like the one given by bajtos on Nov 12.

Thank you @doublemarked , I left a comment in the docs issue where I had picked up the incorrect syntax and CC'ed you on it.

@doublemarked - sorry to bug you again, I'll make sure to jot down everything that I'm unclear on against the doc issue too. But I just can't seem to make some of the simplest logic work.

From the Nov 12 code:
if ( ! req.accessToken) return next();
... always ends up running! So I never get to set the context.

Why would req.accessToken be missing when I know that its specified for sure? Could it have to do with where in the server.js file the following are placed?

// -- Add your pre-processing middleware here --
app.use(loopback.context());
app.use(loopback.token());
app.use(function (req, res, next) {
  console.log('me me me me me me me me');
  console.log(req.accessToken);
  if (!req.accessToken) {
  ...

That seems like the correct placement. How are you specifying the access token? Are all of your loopback modules up to date?

@doublemarked - Yes thanks ... I was passing a junk token ... I didn't realize that a valid object is returned in accessToken so a junk token string won't be printed out at all ... with a valid token its working ... uggh I'm such a n00b.

:) Glad to hear it's working for you.

@doublemarked - i can find the user based on the code from Nov 12 if I use it explicitly in the remote method or the middleware BUT I can't pickup the user set into the context by the middleware:
https://gist.github.com/pulkitsinghal/37c10c30a1a13e9f1db8

If you have any tips, I would appreciate them.

@doublemarked - I had commented out app.use(loopback.context()); and that was apparently the problem because uncommenting it fixed the issue.

BUT I had commented it out because I read that there was a commit to: "Enable the context middleware from loopback.rest" ... @raymondfeng what was that about then?

@pulkitsinghal - sorry, not sure what you're referring to :)

//app.use(loopback.context()); // Commented because: Enable the context middleware from loopback.rest (Raymond Feng)
app.use(loopback.token());

Let me explain. We have added loopback.context to loopback.rest to ensure that all REST applications have the context available, even if they don't add the middleware explicitly.

That cover simple cases.

In advanced use cases, like when you want to add a custom middleware depending on the context, you have to add the context middleware manually and ensure it was added at the right position in the middleware chain (i.e. before the middleware that depends on loopback.getCurrentContext).

Note that loopback.context detects the situation when it is invoked multiple times on the same request and returns immediately in sub-sequent runs.

@bajtos - I follow pretty much of @pulkitsinghal doing, but it's failed.
My version is is v2.9.2 (higher than that in github?). I don't if this issue has been merge to the release version yet.

Whether app.use(loopback.context()) is comment out or not, getCurrentContext() is always _null_

@chinuy - I think that 2.9.2 is the version of the slc command line interface and not the loopback library itself.

To get a better list of what version of loopback code you're running exactly, use:

npm list | grep loopback

... which on my machine yields:

โ”œโ”€โ”ฌ [email protected] 
โ”‚ โ”œโ”€โ”ฌ [email protected] 
โ”‚ โ”œโ”€โ”ฌ [email protected] 
โ”œโ”€โ”ฌ [email protected] 
โ”œโ”€โ”ฌ [email protected] 
โ”‚ โ”œโ”€โ”€ [email protected] 
โ”œโ”€โ”ฌ [email protected] 
โ”œโ”€โ”ฌ [email protected]

Then, if you find yourself nowhere near the latest versions then I think you can use slc update to get them.

Personally, when I had to upgrade, I had created a new loopback project template by using slc loopback then merged the new project's package.json file across directories to my old project ... deleted all the node_modules from the old project and ran npm install again ... but I hope you don't have to do that and slc update should suffice?

Also if you haven't already, have a peek at: http://docs.strongloop.com/display/public/LB/Using+current+context

It doesn't talk about versioning but it does attempt to document the context feature.

It seems like I want to retrieve _loopback.getCurrentContext()_ in remote method and this would fail. Nevertheless, this works with model hook.

@chinuy - I too wanted to get this to work in the remote method and I DID get it to work. Would you like to continue this conversation on Gitter? That way I can help you out.
https://gitter.im/strongloop/loopback#

It seems like I want to retrieve loopback.getCurrentContext() in remote method and this would fail. Nevertheless, this works with model hook.

It's quite hard to help you without seeing the code you are running. If you are using MongoDB, then you may be affected by the following bug: #809.

For those coming on this thread later, I managed to get the req context using the remoteMethod() arguments:

...
accepts: [
        {arg:"req", type:"Object", required: true, http: function(ctx) { return ctx.req; }},
...

Model.myfun = function(req, cb) {
}

This one does it for me:

var loopback = require('loopback');
var user = loopback.getCurrentContext().get('currentUser');

@danmo Sw33t, this works for me. Thank you

LoopBack is really amazing!

I have tested it like this:

var loopback = require('loopback');
    var lctx = loopback.getCurrentContext();
    var userId = lctx.active.http.req.accessToken.userId;
    console.log(userId);

    var loopback = require('loopback');
    var user = loopback.getCurrentContext().get('currentUser');
    console.log(user);

Output is following: user - undefined.

"loopback": "^2.16.3",
    "loopback-boot": "^2.4.0",
    "loopback-component-passport": "^1.3.0",
    "loopback-component-storage": "^1.4.0",
    "loopback-connector-mongodb": "^1.8.0",
    "loopback-datasource-juggler": "^2.25.1",
    "loopback-explorer": "^1.7.2",

I have tested it like this:
(...)
Output is following: user - undefined.
(...)

@ekussberg Please open a new GH issue and provide a sample app reproducing the problem per Wiki instructions.

@bajtos this is the example of code i used:

ShoppingCart.getSessionCart = function (cb) {
    // Get the current session
    var lctx = loopback.getCurrentContext();
    var request = lctx.active.http.req;
    var ipAddress = request.connection.remoteAddress;
    var sessionID = request.sessionID;

    console.log("Get session cart!");
    console.log('From ip address: ', ipAddress);
    console.log('For session: ', sessionID);

  };

  ShoppingCart.remoteMethod(
    'getSessionCart',
    {
      description: "Function used to retrieve the cart for the session",
      returns: {type: 'Object', root: true},
      http: {verb: 'get'}
    }
  );

following -http://docs.strongloop.com/display/public/LB/Using+current+context

  var loopback = require('loopback');
  var user = loopback.getCurrentContext().get('currentUser');     
 console.user(user);

output- undefined

"loopback-boot": "^2.6.5",
"loopback-connector-mongodb": "^1.11.3",
"loopback-datasource-juggler": "^2.33.1",
"serve-favicon": "^2.0.1"

@ppbntl19 - please join https://gitter.im/strongloop/loopback to get quick answers on already closed issues

I am having issues with this implementation using the following:

server/server.js

app.use(loopback.context());
app.use(loopback.token());
app.use(function (req, res, next) {

  accessToken = req.query.access_token || false;
  if (!accessToken) return next();
  var loopbackContext = loopback.getCurrentContext();
  if (loopbackContext) {
    loopbackContext.set('accessToken', accessToken);
    console.log(loopbackContext);
    next();
  }
});

redemption.js

var loopback = require('loopback');
module.exports = function(Redemption) {

  Redemption.updateSubscriberSavings = function(params, callback) {
    redemption = params.redemption;
    var ctx = loopback.getCurrentContext();
    var accessToken = ctx && ctx.get('accessToken') || {};
    console.log(ctx);
 ...

server.js output:

{ name: 'loopback',
  active: { accessToken: 'XU2WDH02ApOHqqFkYeHguoWm9q0x3KtfoJ7YpgrLvNvI3kYvReDFVkEYofwqrETD' },
  _set: [ null ],
  id:
   { create: [Function],
     flags: 15,
     before: [Function],
     after: [Function],
     error: [Function],
     uid: 2,
     data: null } }

model output:

 { name: 'loopback',
  active: {},
  _set: [ null ],
  id:
   { create: [Function],
     flags: 15,
     before: [Function],
     after: [Function],
     error: [Function],
     uid: 2,
     data: null } }

This was working fine on my centos sandbox but it does not work on any of the deployment servers. Both operating systems are CentOS

@joe-at-startupmedia As stated by @pulkitsinghal, please join https://gitter.im/strongloop/loopback to get quick answers on already closed issues.

Ive the same issue

This is how I've implemented it
app.start = function() {
// start the web server
return app.listen(function() {
app.emit('started');
var baseUrl = app.get('url').replace(//$/, '');
console.log('Web server listening at: %s', baseUrl);
if (app.get('loopback-component-explorer')) {
var explorerPath = app.get('loopback-component-explorer').mountPath;
console.log('Browse your REST API at %s%s', baseUrl, explorerPath);
}
});
};

app.use(loopback.context());
app.use(loopback.token());
app.use(function setCurrentUser(req, res, next) {
if (!req.accessToken) {
return next();
}
app.models.User.findById(req.accessToken.userId, function(err, user) {
if (err) {
return next(err);
}
if (!user) {
return next(new Error('No user with this access token was found.'));
}
var loopbackContext = loopback.getCurrentContext();
if (loopbackContext) {
loopbackContext.set('currentUser', user);
}
next();
});
});

However i keep getting the currentUser as null,

This seems like an unresolved bug.....or maybe I'm doing something horribly wrong!!!!

@drmikecrowe best solution IMHO, :+1: for that.

@drmikecrowe solution resolves what was asked in this issue.

To receive the http context, there are potentially two options (assuming there is no reliable way to retrieve the current context associated with the invocation chain):

  1. Via the parameter injection. What @drmikecrowe proposes supports that.
  2. Via the model class injection. You can get it using this.context.One of the limitation is that the model class is a singleton. We can relax that by supporting a setting so that we can create new (subclassing) or reuse pooled classes of the model as the receiver. That will allow the runtime to inject context into the class object itself and remote methods can access it via this.context.

loopback.getCurrentContext() returns null in boot script itself...

Could someone please explain me @drmikecrowe method? I'm really not sure where the whole 'accepts' part should go.

@johannesjo -- I added the current user to the current context in server/server.js like @Sandy1088 above:

app.use(function setCurrentUser(req, res, next) {
    if (!req.accessToken) {
        return next();
    }
    app.models.User.findById(req.accessToken.userId, function(err, user) {
        if (err) {
            return next(err);
        }
        if (!user) {
            return next(new Error('No user with this access token was found.'));
        }
        var loopbackContext = loopback.getCurrentContext();
        if (loopbackContext) {
            req.accessToken.currentUser = user;
            loopbackContext.set('currentUser', user);
        }
        next();
    });
});

My other solution was in remote methods:

user.remoteMethod('methodName', {
        accepts: [
            {arg: 'req', type: 'object', required: true, http: {source: 'req'}}
        ],
        returns: {arg: 'user', type: 'object', root: true},
        http:    {verb: 'post', path: '/methodName'}
    }
);


user.methodName = Bluebird.promisify(function methodName(req, cb) {
    var context            = loopback.getCurrentContext();
    var currentUser        = req.accessToken && req.accessToken.currentUser || context && context.get('currentUser');
    // .....
});

@drmikecrow thank you very much for explaining!

someModel.getUserId = function(id, cb) {
    cb(null, id);
};

someModel.getUserId(
    'getClientUser',
    {
        accepts: {arg: 'id', type: 'string'},
        returns: {arg: 'User', type: 'array'},
        http: {verb: 'get'}
    }
);

someModel.beforeRemote('getUserId', function (context, unused, next) {
    var req = context.req;

    req.remotingContext.args.id = req.accessToken.userId;

    next();
});

I'm having this problem when owner has the same id.. in example a user may want to update the profile data... so i check if the user.id in update is the same in accesstoken

user.beforeRemote('upsert', function (context, model, next) {
  var req = context.req;
  var userobject = context.req.body;
  var userID = req.accessToken.userId;
  if(userobject && userobject._id!=null && userobject._id!=req.accessToken.userId){
      var error = new Error();
      error.statusCode = 401;
      error.message = 'Authorization Required';
      error.code = 'AUTHORIZATION_REQUIRED';
      next(error);
  } else next();
 });

The solution for get current authenticated user:

var loopback = require('loopback');
module.exports = function(Model) {    
    Model.observe('before save', function(ctx, next) {
         if (ctx.instance)` {
            var ctxs = loopback.getCurrentContext();
            ctx.instance.date = new Date();
            ctx.instance.id_user = ctxs.active.accessToken["userId"];
            console.log('currentUser.infotmation: ', ctxs.active.accessToken);
        } else {
        }
        next();
    });
};

remsume :

var loopback = require('loopback');
var ctxs = loopback.getCurrentContext();
ctxs.active.accessToken

// Console output for ctxs.active.accessToken

{ id: 'vcrl8INUAk99MCbTHPB8zA9fuR0gDCLGi0f4DbqYvAIoMOLPVrc4PVYuWDJLUJGv',
  ttl: 1209600,
  created: Tue Jun 14 2016 23:48:08 GMT+0000 (UTC),
  userId: 13 }

@SamuelBonilla do you have to set any other param in server.js? I have exactly the same code and it's returning null

@renanwilliam I don't have other code in serrver.js. getCurrentContext().active.accessToken return null when the user is not authenticated you need to pass the accessToken to the api endponint

maybe 'downgrade' your nodejs to v0.12 and see if the issue existed. You may refer this discussion:
https://github.com/strongloop/loopback/issues/878#issuecomment-215673421

For those looking for a solution to finding out the authenticated user in a remote method (now that currentContext is deprecated), the groundwork is laid out at the following link: https://docs.strongloop.com/display/public/LB/Remote+methods#Remotemethods-HTTPmappingofinputarguments

Here is my example snippet:

  MyObject.remoteMethod(
    'myMethod',
    {
      http: {path: '/myMethod', verb: 'get'},
      accepts: [
        {arg: 'someArgument', type: 'string'},
        {
          arg: 'accessToken',
          type: 'object',
          http: function(ctx) {
            return ctx.req.accessToken;
          }
        }
        ],
      returns: {arg: 'someResponse', type: 'string'}
    }
  );
  MyObject.myMethod = function(someArgument, accessToken, cb) {
    // ...
    // accessToken.userId
    // ...
    cb(null, "Response blah blah...");
  }

Any other solution to get current connected user inside the model?

@jankcat Thanks, this example worked perfectly! Exactly what I was looking for after reading too many pages of back and forth on the issue. I couldn't figure this out from the current 3.0 documentation, so I created a pull request to add your example to the documentation.

@ataft thank you for the pull request to improve our documentation!

As I commented there, while the example shown in this issue is a valid one, it is also using a different approach than we implemented for context propagation.

Here is a better example:

// common/models/my-model.js
module.exports = function(MyModel) {
  MyModel.log = function(messageId, options) {
    const Message = this.app.models.Message;
    return Message.findById(messageId, options)
      .then(msg => {
        const userId = options && options.accessToken && options.accessToken.userId;
        const user = userId ? 'user#' + userId : '<anonymous>';
        console.log('(%s) %s', user, msg.text));
      });
  };

// common/models/my-model.json
{
  "name": "MyModel",
  // ...
  "methods": {
    "log": {
      "accepts": [
        {"arg": "messageId", "type": "number", "required": true},
        {"arg": "options", "type": "object", "http": "optionsFromRequest"}
      ],
      "http": {"verb": "POST", "path": "/log/:messageId"}
    }
  }
}

I'm probably misunderstanding something here, as I'm pretty new to Loopback. But do the examples above imply that _every_ model method that needs access to the authenticated user must implement this explicitly? This seems like a lot of overhead for an extremely common situation. Is there any way to just make the authenticated user, if it exists, available in ALL remote methods?

I'm probably misunderstanding something here, as I'm pretty new to Loopback. But do the examples above imply that every model method that needs access to the authenticated user must implement this explicitly?

Yes, that's correct - see http://loopback.io/doc/en/lb3/Using-current-context.html

This seems like a lot of overhead for an extremely common situation. Is there any way to just make the authenticated user, if it exists, available in ALL remote methods?

We tried to use continuation-local-storage as a Node.js replacement for ThreadLocalStorage, unfortunately it turned out there is no reliable implementation of CLS available in Node.js land :(

The solution based on "options" argument is the only robust and reliable approach we have found. However, if you can find a better way, then please let us know!

@bajtos Ok, thanks.

I haven't looked under the hood, so not sure how complicated it would be to implement, but one thought would be to offer a setting at the model level that populates the options from request automatically for all methods. It just seems like such a common need โ€”ย I can't remember ever working on an app where I _didn't_ need access to the user in the controllers (remote methods, in this case). Maybe something like:

// common/models/my-model.json
{
  "name": "MyModel",
  "includeOptionsFromRequest": true,
  ...
}

Coming from 2.x + current context I am not able to port this implementation to the following cases, and the documentations concept is soo confusing...
options object
optionsFromRequest (that is converted) what? Really? What about options not coming from a request but from an internal test run?

.getCurrentContext
.runInContext (for testing)
.set('xx',value)

the example about getting current user

// common/models/my-model.js
module.exports = function(MyModel) {
  MyModel.observe('access', function(ctx, next) {
    const token = ctx.options && ctx.options.accessToken;
    const userId = token && token.userId;
    const user = userId ? 'user#' + userId : '<anonymous>';

    const modelName = ctx.Model.modelName;
    const scope = ctx.where ? JSON.stringify(ctx.where) : '<all records>';
    console.log('%s: %s accessed %s:%s', new Date(), user, modelName, scope);
    next();
  });
};

is not in relation with
2.x middleware, it does not even return current user object but some token and thats it,

  server.middleware('auth', loopback.token({
                                             model: server.models.accessToken,
                                             currentUserLiteral: 'me'
                                           }));

according to #569 is not acceptable since all framework has this very basic foundation to simply have a shorthand for accessing the current user.

Is there an example of middleware or component for 3.x so we can simply, by using options can retrieve currentUser reference? I mean in a large app there are hundreds of remote methods, shall we need to rewrite all of them? I'm sure there is a better way other than messing up the community with bad design and another bad design not even producing a really usable documentation for cases to be replaced easily. Not even a migration guide.

@barocsi I am afraid I don't understand your comment very well. I'll try to answer as best as I can.

What about options not coming from a request but from an internal test run?

.getCurrentContext
.runInContext (for testing)
.set('xx',value)

When you are invoking a method from code (e.g. in a test), you need to build and pass the options object explicitly. For example:

it('does something with current user', function() {
  return User.login(CREDENTIALS)
    .then(token => {
      const options = {accessToken: token};
      return MyModel.customMethod(someArgs, options);
    })
    .then(result => {
      // verify customMethod worked as expected
    });

it does not even return current user object but some token

The options value contain the same token as set by LoopBack 2.x. You can retrieve the current user e.g. by calling options.accessToken.user() method.

I mean in a large app there are hundreds of remote methods, shall we need to rewrite all of them?

Yes, all remote methods that want to access current context must be modified to accept options argument.

The lack of a solution for ThreadLocalStorage is a limitation of Node.js platform. There is some work in progress on this front, but it's still far from getting into a state where it could be used in production. I am highly encouraging you to get involved in the discussion and work in Node.js core, here are few links to get you started:

@bajtos - thank you for this information. Two points that aren't entirely clear -

1) to make use of options, will I need to migrate from LoopBack 2.x to LoopBack 3.0?

2) Are only operation hooks affected by this issue ('before save', 'after save', etc), or are all remote methods subject to continuation passing issues? (i.e. would I have to update every place where I'm currently referencing req.accessToken?)

@gdeckert

to make use of options, will I need to migrate from LoopBack 2.x to LoopBack 3.0?

You can pass context via options in LoopBack 2.x as long as you enable this feature, see http://loopback.io/doc/en/lb3/Using-current-context.html#annotate-options-parameter-in-remoting-metadata:

_In LoopBack 2.x, this feature is disabled by default for compatibility reasons. To enable, add "injectOptionsFromRemoteContext": true to your model JSON file._

Are only operation hooks affected by this issue ('before save', 'after save', etc), or are all remote methods subject to continuation passing issues? (i.e. would I have to update every place where I'm currently referencing req.accessToken?)

I think the code which can access accessToken directly via the incomingreq object should not need any changes. You _can_ upgrade it to use options.accessToken instead of req.accessToken, but it shouldn't be required. I use "should" because it's hard to tell for me without seeing your code.

Elegant way of getting userId from acccessToken by request object in remote method:

  MyModel.register = function(req, param, cb) {
    var userId = req.accessToken.userId;

    console.log(userId); 
    cb(null, ...);
  };

  MyModel.remoteMethod(
    'register',
    {
      accepts: [
        { arg: 'req', type: 'object', http: {source: 'req'} }, // <----
        { arg: 'param', type: 'string', required: true },
      ]
    }
Was this page helpful?
0 / 5 - 0 ratings