You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A hacky workaround: use the new header argument source (see strongloop/strong-remoting#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 userapp.use(function(req,res,next){// First, get the access token, either from the url or from the headersvartokenId=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 tokenelseif(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 appapp.currentUser=false;if(tokenId){varUserModel=app.models.User;// Logic borrowed from user.js -> User.logout()UserModel.relations.accessTokens.modelTo.findById(tokenId,function(err,accessToken){if(err)returnnext(err);if(!accessToken)returnnext(newError('could not find accessToken'));// Look up the user associated with the accessTokenUserModel.findById(accessToken.userId,function(err,user){if(err)returnnext(err);if(!user)returnnext(newError('could not find a valid user'));app.currentUser=user;next();});});}// If no tokenId was found, continue without waitingelse{next();}});
Reacted by nitishChandel, Jason Brewer, DevHat13, Sachin Negi and BAH Djibril
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
Reacted by Sagar Shelar, Mariano Benedettini, Bhavish Pulnah and Black Jack
@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 userapp.use(function(req,res,next){app.currentUser=null;// Retrieve the access token used in this requestvarAccessTokenModel=app.models.AccessToken;AccessTokenModel.findForRequest(req,{},function(err,token){if(err)returnnext(err);if(!token)returnnext();// No need to throw an error here// Logic borrowed from user.js -> User.logout() to get access token objectvarUserModel=app.models.User;UserModel.relations.accessTokens.modelTo.findById(token.id,function(err,accessToken){if(err)returnnext(err);if(!accessToken)returnnext(newError('could not find the given token'));// Look up the user associated with the access tokenUserModel.findById(accessToken.userId,function(err,user){if(err)returnnext(err);if(!user)returnnext(newError('could not find a valid user with the given token'));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)returnnext();app.models.User.findById(req.accessToken.userId,function(err,user){if(err)returnnext(err);if(!user)returnnext(newError('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.
@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)returnnext();app.models.User.findById(req.accessToken.userId,function(err,user){if(err)returnnext(err);if(!user)returnnext(newError('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.
Reacted by Timothee Moulin, Thanh Nguyen, Evgeniy and Muhammad Ali Shahzad
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)returnnext();app.models.User.findById(req.accessToken.userId,function(err,user){if(err)returnnext(err);if(!user)returnnext(newError('No user with this access token was found.'));res.locals.currentUser=user;varloopbackContext=loopback.getCurrentContext();if(loopbackContext)loopbackContext.set('currentUser',user);next();});});// anywhere in the appvarctx=loopback.getCurrentContext();varcurrentUser=ctx&&ctx.get('currentUser');
Reacted by Jake Foraker, Igor Moiseev, Varun Ramanujam, Tiravat Thaisubvorakul, Willson Fernandes and Sachin Negi
@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
@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.
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?
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!
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:
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?
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.
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(){returnUser.login(CREDENTIALS).then(token=>{constoptions={accessToken: token};returnMyModel.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 -
to make use of options, will I need to migrate from LoopBack 2.x to LoopBack 3.0?
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?)
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.
Reacted by Pulkit Singhal, noroot, Lisa Riggle, Paulo McNally, Carlos Cárcamo, Jelil Adesina, Octavian Susnea, Heartlander, shaheero, Rigo B Castro and 26 more
Reacted by Heartlander, Rigo B Castro, panlilu, Piotr Seefeld, Matias, Jackson Vaughan, Diky Arga, Charlie Schliesser, WilyWork and Prayudi Tirta Bayu
Reacted by Jelil Adesina, Heartlander, Rigo B Castro, Matias, Elías Leyton, Jackson Vaughan, Marshall Chan, Diky Arga, Charlie Schliesser, WilyWork and 1 more
Activity
meng-zhang commentedon Sep 19, 2014
In the beforeRemote, you can find the userId in ctx.res.token.userId
Not sure this is the best solution or not.
cajoy commentedon Sep 19, 2014
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
bajtos commentedon Sep 20, 2014
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 #337
bajtos commentedon Sep 20, 2014
A hacky workaround: use the new
headerargument source (see strongloop/strong-remoting#104) to get the access-token from the request header, then look up theuserIdby querying access tokens.brainthinks commentedon Oct 27, 2014
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.
ffflabs commentedon Oct 27, 2014
This is the workaround I used on a custom route /whoami
this route is inside /server/boot/root.js
brainthinks commentedon Oct 28, 2014
@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:
bajtos commentedon Oct 29, 2014
I don't recommend attaching the user to the
appobject, it will stop working once you have more than one request being handled in parallel.Setting that aside, here is a simpler solution:
But as I said, don't do that. It creates a hard to debug bug where Request A sees
app.currentUserfilled by Request B in case these requests arrive at the server (almost) in parallel.brainthinks commentedon Oct 29, 2014
@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:
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.
doublemarked commentedon Nov 10, 2014
@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.
brainthinks commentedon Nov 12, 2014
@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:This is parallel-safe and can be access anywhere you have access to ctx or res.
bajtos commentedon Nov 12, 2014
And if you want to use the new context functionality, you can store the user in the loopback context too:
doublemarked commentedon Nov 12, 2014
Beautiful, thank you guys.
@bajtos What is this new context functionality and where is it documented? It is new as of what version?
47 remaining items
renanwilliam commentedon Aug 3, 2016
@SamuelBonilla do you have to set any other param in server.js? I have exactly the same code and it's returning null
SamuelBonilla commentedon Aug 3, 2016
@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
chinuy commentedon Aug 3, 2016
maybe 'downgrade' your nodejs to v0.12 and see if the issue existed. You may refer this discussion:
#878 (comment)
jankcat commentedon Nov 2, 2016
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:
yagobski commentedon Nov 3, 2016
Any other solution to get current connected user inside the model?
ataft commentedon Jan 12, 2017
@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.
bajtos commentedon Jan 13, 2017
@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:
jmwohl commentedon Jan 18, 2017
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?
bajtos commentedon Jan 19, 2017
Yes, that's correct - see http://loopback.io/doc/en/lb3/Using-current-context.html
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!
jmwohl commentedon Jan 19, 2017
@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:
barocsi commentedon Jan 28, 2017
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
is not in relation with
2.x middleware, it does not even return current user object but some token and thats it,
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.
bajtos commentedon Jan 30, 2017
@barocsi I am afraid I don't understand your comment very well. I'll try to answer as best as I can.
When you are invoking a method from code (e.g. in a test), you need to build and pass the
optionsobject explicitly. For example:The
optionsvalue contain the same token as set by LoopBack 2.x. You can retrieve the current user e.g. by callingoptions.accessToken.user()method.Yes, all remote methods that want to access current context must be modified to accept
optionsargument.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:
gdeckert commentedon Feb 15, 2017
@bajtos - thank you for this information. Two points that aren't entirely clear -
to make use of options, will I need to migrate from LoopBack 2.x to LoopBack 3.0?
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?)
bajtos commentedon Feb 17, 2017
@gdeckert
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.
I think the code which can access
accessTokendirectly via the incomingreqobject should not need any changes. You can upgrade it to useoptions.accessTokeninstead ofreq.accessToken, but it shouldn't be required. I use "should" because it's hard to tell for me without seeing your code.antarasi commentedon Feb 22, 2017
Elegant way of getting userId from acccessToken by request object in remote method: