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