Skip to content

Files

Latest commit

 

History

History
158 lines (126 loc) · 6.17 KB

Using-current-context.md

File metadata and controls

158 lines (126 loc) · 6.17 KB
title redirect_from lang layout keywords tags sidebar permalink summary
Using current context
/doc/en/lb2/Using%20current%20context.html
en
page
LoopBack
lb3_sidebar
/doc/en/lb3/Using-current-context.html

LoopBack applications sometimes need to access context information to implement the business logic, for example to:

  • Access the currently logged-in user.
  • Access the HTTP request (such as URL and headers).

A typical request to invoke a LoopBack model method travels through multiple layers with chains of asynchronous callbacks. It's not always possible to pass all the information through method parameters. 

LoopBack 2.x introduced current-context APIs using the module continuation-local-storage to provide a context object preserved across asynchronous operations. Unfortunately, this module is not reliable and has many known problems (for example, see issue #59). As a result, the current-context feature does not work in many situations, see loopback-context issues and related issues in loopback.

To address this problem, LoopBack 3.0 moves all current-context-related code to loopback-context module and removed all current-context APIs (see Release Notes).

However, applications clearly need to acces information like the currently logged-in user in application logic, for example in Operation hooks. Until there is a reliable implementation of continuation-local-storage available for Node.js, explicitly pass any additional context via options parameter of (remote) methods.

All built-in methods like PersistedModel.find or PersistedModel.create were already modified to accept an optional options argument.

Operation hooks expose the options argument as context.options.

You must safely initialize the options parameter when a method is invoked via REST API, ensuring that clients cannot override sensitive information like the currently logged-in user. Doing so requires two steps:

  • Annotate "options" parameter in remoting metadata
  • Customize the value provided to "options"

Annotate "options" parameter in remoting metadata

Methods accepting an options argument must declare this argument in their remoting metadata and set the http property to the special string value "optionsFromRequest".

{
  "arg": "options",
  "type": "object",
  "http": "optionsFromRequest"
}

Under the hood, Model.remoteMethod converts this special string value to a function that will be called by strong-remoting for each incoming request to build the value for this parameter.

{% include tip.html content=' Computed "accepts" parameters have been around for a while and they are well supported by LoopBack tooling. For example, the Swagger generator excludes computed properties from the API endpoint description. As a result, the "options" parameter will not be described in the Swagger documentation. ' %}

All built-in method have been already modified to include this new "options" parameter.

{% include note.html content=' In LoopBack 2.x, this feature is disabled by default for compatibility reasons. To enable, add "injectOptionsFromRemoteContext": true to your model JSON file. ' %}

Customize the value provided to "options"

When strong-remoting resolves the "options" argument, it calls model's createOptionsFromRemotingContext method. The default implementation of this method returns an object with a single property accessToken containing the AccessToken instance used to authenticate the request.

There are several ways to customize this value:

  • Override createOptionsFromRemotingContext in your model.
  • Use a "beforeRemote" hook.
  • Use a custom strong-remoting phase.

Override createOptionsFromRemotingContext in your model

MyModel.createOptionsFromRemotingContext = function(ctx) {
  var base = this.base.createOptionsFromRemotingContext(ctx);
  return extend(base, {
    currentUserId: base.accessToken && base.accessToken.userId,
  });
};

A better approach is to write a mixin that overrides this method and that can be shared between multiple models.

Use a "beforeRemote" hook

Because the "options" parameter is a regular method parameter, you can access it from remote hooks via ctx.args.options.

MyModel.beforeRemote('saveOptions', function(ctx, unused, next) {
  if (!ctx.args.options.accessToken) return next();
  User.findById(ctx.args.options.accessToken.userId, function(err, user) {
    if (err) return next(err);
    ctx.args.options.currentUser = user;
    next();
  });
})

Again, a hook like this can be reused by placing the code in a mixin.

Note that remote hooks are executed in order controlled by LoopBack framework, which may be different from the order in which you need to modify the options parameter and then read the modified values. Please use the next suggestion if this order matters in your application.

Use a custom strong-remoting phase

Internally, strong-remoting uses phases similar to middleware phases. The framework defines two built-in phases: auth and invoke. All remote hooks are run in the second phase invoke.

Applications can define a custom phase to run code before any remote hooks are invoked, such code can be placed in a boot script for example.

module.exports = function(app) {
  app.remotes().phases
    .addBefore('invoke', 'options-from-request')
    .use(function(ctx, next) {
      if (!ctx.args.options.accessToken) return next();
      User.findById(ctx.args.options.accessToken.userId, function(err, user) {
        if (err) return next(err);
        ctx.args.options.currentUser = user;
        next();
      });
    });
};