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

Closed
cajoy opened this Issue Sep 18, 2014 · 75 comments

Comments

Projects
None yet
@cajoy

cajoy commented Sep 18, 2014

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?

@meng-zhang

This comment has been minimized.

Show comment
Hide comment
@meng-zhang

meng-zhang 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.

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

@cajoy

This comment has been minimized.

Show comment
Hide comment
@cajoy

cajoy 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

cajoy commented 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

This comment has been minimized.

Show comment
Hide comment
@bajtos

bajtos Sep 20, 2014

Member

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

Member

bajtos commented 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 bajtos added the question label Sep 20, 2014

@bajtos bajtos closed this Sep 20, 2014

@bajtos

This comment has been minimized.

Show comment
Hide comment
@bajtos

bajtos Sep 20, 2014

Member

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.

Member

bajtos commented Sep 20, 2014

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.

@brainthinks

This comment has been minimized.

Show comment
Hide comment
@brainthinks

brainthinks 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.

// 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();
  }
});

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();
  }
});
@amenadiel

This comment has been minimized.

Show comment
Hide comment
@amenadiel

amenadiel Oct 27, 2014

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

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

@brainthinks

This comment has been minimized.

Show comment
Hide comment
@brainthinks

brainthinks 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:

// 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();
      });

    });
  });

});

@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();
      });

    });
  });

});
@bajtos

This comment has been minimized.

Show comment
Hide comment
@bajtos

bajtos Oct 29, 2014

Member

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.

Member

bajtos commented Oct 29, 2014

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.

@brainthinks

This comment has been minimized.

Show comment
Hide comment
@brainthinks

brainthinks 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:

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.

@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.

@doublemarked

This comment has been minimized.

Show comment
Hide comment
@doublemarked

doublemarked 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.

@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

This comment has been minimized.

Show comment
Hide comment
@brainthinks

brainthinks 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:

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.

@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.

@bajtos

This comment has been minimized.

Show comment
Hide comment
@bajtos

bajtos Nov 12, 2014

Member

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');
Member

bajtos commented Nov 12, 2014

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');
@doublemarked

This comment has been minimized.

Show comment
Hide comment
@doublemarked

doublemarked 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?

Beautiful, thank you guys.

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

@bajtos

This comment has been minimized.

Show comment
Hide comment
@bajtos

bajtos Nov 12, 2014

Member

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.

Member

bajtos commented Nov 12, 2014

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.

@bajtos

This comment has been minimized.

Show comment
Hide comment
@bajtos

bajtos Nov 12, 2014

Member

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

Member

bajtos commented Nov 12, 2014

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

@doublemarked

This comment has been minimized.

Show comment
Hide comment
@doublemarked

doublemarked Nov 12, 2014

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

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

@pulkitsinghal

This comment has been minimized.

Show comment
Hide comment
@pulkitsinghal

pulkitsinghal Dec 13, 2014

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

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

@doublemarked

This comment has been minimized.

Show comment
Hide comment
@doublemarked

doublemarked Dec 13, 2014

@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.

@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.

@pulkitsinghal

This comment has been minimized.

Show comment
Hide comment
@pulkitsinghal

pulkitsinghal Dec 14, 2014

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

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

@doublemarked

This comment has been minimized.

Show comment
Hide comment
@doublemarked

doublemarked Dec 14, 2014

It's a handle for the loopback module,

var loopback = require('loopback');

It's a handle for the loopback module,

var loopback = require('loopback');
@pulkitsinghal

This comment has been minimized.

Show comment
Hide comment
@pulkitsinghal

pulkitsinghal Dec 14, 2014

@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?

@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?

@doublemarked

This comment has been minimized.

Show comment
Hide comment
@doublemarked

doublemarked Dec 14, 2014

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.

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.

@pulkitsinghal

This comment has been minimized.

Show comment
Hide comment
@pulkitsinghal

pulkitsinghal Dec 14, 2014

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.

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.

@pulkitsinghal

This comment has been minimized.

Show comment
Hide comment
@pulkitsinghal

pulkitsinghal Dec 14, 2014

@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) {
  ...

@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) {
  ...
@doublemarked

This comment has been minimized.

Show comment
Hide comment
@doublemarked

doublemarked Dec 14, 2014

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

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

@pulkitsinghal

This comment has been minimized.

Show comment
Hide comment
@pulkitsinghal

pulkitsinghal Dec 14, 2014

@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.

@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.

@doublemarked

This comment has been minimized.

Show comment
Hide comment
@doublemarked

doublemarked Dec 14, 2014

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

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

@pulkitsinghal

This comment has been minimized.

Show comment
Hide comment
@pulkitsinghal

pulkitsinghal Dec 15, 2014

@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 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.

@pulkitsinghal

This comment has been minimized.

Show comment
Hide comment
@pulkitsinghal

pulkitsinghal Dec 15, 2014

@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?

@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?

@doublemarked

This comment has been minimized.

Show comment
Hide comment
@doublemarked

doublemarked Dec 15, 2014

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

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

@bajtos

This comment has been minimized.

Show comment
Hide comment
@bajtos

bajtos Dec 16, 2014

Member
//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.

Member

bajtos commented Dec 16, 2014

//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.

@chinuy

This comment has been minimized.

Show comment
Hide comment
@chinuy

chinuy Dec 22, 2014

@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 commented Dec 22, 2014

@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

@pulkitsinghal

This comment has been minimized.

Show comment
Hide comment
@pulkitsinghal

pulkitsinghal Dec 22, 2014

@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:

├─┬ loopback@2.8.5 
│ ├─┬ loopback-connector-remote@1.0.3 
│ ├─┬ loopback-phase@1.2.0 
├─┬ loopback-boot@2.5.1 
├─┬ loopback-datasource-juggler@2.12.0 
│ ├── loopback-connector@1.2.0 
├─┬ loopback-explorer@1.6.2 
├─┬ loopback-testing@1.0.4

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.

@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:

├─┬ loopback@2.8.5 
│ ├─┬ loopback-connector-remote@1.0.3 
│ ├─┬ loopback-phase@1.2.0 
├─┬ loopback-boot@2.5.1 
├─┬ loopback-datasource-juggler@2.12.0 
│ ├── loopback-connector@1.2.0 
├─┬ loopback-explorer@1.6.2 
├─┬ loopback-testing@1.0.4

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.

@chinuy

This comment has been minimized.

Show comment
Hide comment
@chinuy

chinuy Dec 22, 2014

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

chinuy commented Dec 22, 2014

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

@pulkitsinghal

This comment has been minimized.

Show comment
Hide comment
@pulkitsinghal

pulkitsinghal Dec 22, 2014

@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#

@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#

@rhymn

This comment has been minimized.

Show comment
Hide comment
@rhymn

rhymn Jun 12, 2015

LoopBack is really amazing!

rhymn commented Jun 12, 2015

LoopBack is really amazing!

@ekussberg

This comment has been minimized.

Show comment
Hide comment
@ekussberg

ekussberg Jun 29, 2015

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:

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",
@bajtos

This comment has been minimized.

Show comment
Hide comment
@bajtos

bajtos Jun 30, 2015

Member

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.

Member

bajtos commented Jun 30, 2015

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.

@ekussberg

This comment has been minimized.

Show comment
Hide comment
@ekussberg

ekussberg Jul 2, 2015

@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'}
    }
  );

@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'}
    }
  );
@ppbntl19

This comment has been minimized.

Show comment
Hide comment
@ppbntl19

ppbntl19 Jul 23, 2015

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"

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"
@pulkitsinghal

This comment has been minimized.

Show comment
Hide comment
@pulkitsinghal

pulkitsinghal Jul 23, 2015

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

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

@joe-at-startupmedia

This comment has been minimized.

Show comment
Hide comment
@joe-at-startupmedia

joe-at-startupmedia Oct 14, 2015

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

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

@superkhau

This comment has been minimized.

Show comment
Hide comment
@superkhau

superkhau Oct 14, 2015

Contributor

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

Contributor

superkhau commented Oct 14, 2015

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

@psi-4ward

This comment has been minimized.

Show comment
Hide comment
@psi-4ward

psi-4ward Oct 19, 2015

Ive the same issue

Ive the same issue

@SandeshSarfare

This comment has been minimized.

Show comment
Hide comment
@SandeshSarfare

SandeshSarfare Jan 14, 2016

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 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,

@SandeshSarfare

This comment has been minimized.

Show comment
Hide comment
@SandeshSarfare

SandeshSarfare Jan 14, 2016

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

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

@drish

This comment has been minimized.

Show comment
Hide comment
@drish

drish Jan 26, 2016

@drmikecrowe best solution IMHO, 👍 for that.

drish commented Jan 26, 2016

@drmikecrowe best solution IMHO, 👍 for that.

@drish

This comment has been minimized.

Show comment
Hide comment
@drish

drish Jan 26, 2016

@drmikecrowe solution resolves what was asked in this issue.

drish commented Jan 26, 2016

@drmikecrowe solution resolves what was asked in this issue.

@raymondfeng

This comment has been minimized.

Show comment
Hide comment
@raymondfeng

raymondfeng Jan 26, 2016

Member

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.
Member

raymondfeng commented Jan 26, 2016

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.
@SandeshSarfare

This comment has been minimized.

Show comment
Hide comment
@SandeshSarfare

SandeshSarfare Jan 27, 2016

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

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

@johannesjo

This comment has been minimized.

Show comment
Hide comment
@johannesjo

johannesjo Feb 11, 2016

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

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

@drmikecrowe

This comment has been minimized.

Show comment
Hide comment
@drmikecrowe

drmikecrowe Feb 12, 2016

@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');
    // .....
});

@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');
    // .....
});
@johannesjo

This comment has been minimized.

Show comment
Hide comment
@johannesjo

johannesjo Feb 12, 2016

@drmikecrow thank you very much for explaining!

@drmikecrow thank you very much for explaining!

@Mad-Head

This comment has been minimized.

Show comment
Hide comment
@Mad-Head

Mad-Head Feb 16, 2016

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();
});
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();
});
@mariohmol

This comment has been minimized.

Show comment
Hide comment
@mariohmol

mariohmol Feb 22, 2016

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();
 });

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();
 });
@SamuelBonilla

This comment has been minimized.

Show comment
Hide comment
@SamuelBonilla

SamuelBonilla Jun 15, 2016

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 commented Jun 15, 2016

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 }
@renanwilliam

This comment has been minimized.

Show comment
Hide comment
@renanwilliam

renanwilliam 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 do you have to set any other param in server.js? I have exactly the same code and it's returning null

@SamuelBonilla

This comment has been minimized.

Show comment
Hide comment
@SamuelBonilla

SamuelBonilla 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

SamuelBonilla commented 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

This comment has been minimized.

Show comment
Hide comment
@chinuy

chinuy Aug 3, 2016

maybe 'downgrade' your nodejs to v0.12 and see if the issue existed. You may refer this discussion:
#878 (comment)

chinuy commented Aug 3, 2016

maybe 'downgrade' your nodejs to v0.12 and see if the issue existed. You may refer this discussion:
#878 (comment)

urgent added a commit to urgent/loopback-angular-admin that referenced this issue Oct 12, 2016

Post Authorization -- Show Endpoint
Created new endpoint for authenticated lookup by post owner. Only want
post owners to see their posts, not others.

First step was accessing context user id. Note that app is available in req
strongloop/loopback#569

Next was looking up only userId Post
http://loopback.io/doc/en/lb2/Fields-filter.html
http://loopback.io/doc/en/lb2/Querying-data.html

And returning array from remote method, done by passing it to cb function after first paramter,
which is for error message:
http://loopback.io/doc/en/lb2/Remote-methods.html\#returning-data-outside-of-a-json-field
@jankcat

This comment has been minimized.

Show comment
Hide comment
@jankcat

jankcat 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:

  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...");
  }

jankcat commented 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:

  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...");
  }
@yagobski

This comment has been minimized.

Show comment
Hide comment
@yagobski

yagobski Nov 3, 2016

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

yagobski commented Nov 3, 2016

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

@ataft

This comment has been minimized.

Show comment
Hide comment
@ataft

ataft 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.

ataft commented 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

This comment has been minimized.

Show comment
Hide comment
@bajtos

bajtos Jan 13, 2017

Member

@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"}
    }
  }
}
Member

bajtos commented 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:

// 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"}
    }
  }
}
@jmwohl

This comment has been minimized.

Show comment
Hide comment
@jmwohl

jmwohl 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?

jmwohl commented 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

This comment has been minimized.

Show comment
Hide comment
@bajtos

bajtos Jan 19, 2017

Member

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!

Member

bajtos commented Jan 19, 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?

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!

@jmwohl

This comment has been minimized.

Show comment
Hide comment
@jmwohl

jmwohl 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:

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

jmwohl commented 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:

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

This comment has been minimized.

Show comment
Hide comment
@barocsi

barocsi 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

// 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 commented 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

// 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.

@bajtos

This comment has been minimized.

Show comment
Hide comment
@bajtos

bajtos Jan 30, 2017

Member

@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:

Member

bajtos commented 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.

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:

@gdeckert

This comment has been minimized.

Show comment
Hide comment
@gdeckert

gdeckert Feb 15, 2017

@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?)

@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?)

@bajtos

This comment has been minimized.

Show comment
Hide comment
@bajtos

bajtos Feb 17, 2017

Member

@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.

Member

bajtos commented Feb 17, 2017

@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.

@antarasi

This comment has been minimized.

Show comment
Hide comment
@antarasi

antarasi Feb 22, 2017

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 },
      ]
    }

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 },
      ]
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment