-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to get current authenticated user #569
Comments
In the beforeRemote, you can find the userId in ctx.res.token.userId |
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 #337 |
A hacky workaround: use the new |
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
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 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 |
@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 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? |
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 ( |
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 |
@pulkitsinghal - You need to have a |
@doublemarked - But I just don't know how to get at the |
It's a handle for the loopback module,
|
@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? |
Well, indeed I don't think
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: 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?
|
That seems like the correct placement. How are you specifying the access token? Are all of your loopback modules up to date? |
This is how I've implemented it app.use(loopback.context()); 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, 👍 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):
|
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:
My other solution was in remote methods:
|
@drmikecrow thank you very much for explaining! |
|
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
|
The solution for get current authenticated user:
remsume :
// Console output for ctxs.active.accessToken
|
@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: |
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? |
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! |
@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... .getCurrentContext the example about getting current user
is not in relation with
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.
When you are invoking a method from code (e.g. in a test), you need to build and pass the 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
});
The
Yes, all remote methods that want to access current context must be modified to accept 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 -
|
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 |
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 },
]
} |
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
spent 3 hours on it and can't find easy solution... Basically how can I get user ID from any place in the code?
The text was updated successfully, but these errors were encountered: