Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions pages/en/lb3/Accessing-related-models.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ summary: For related models, LoopBack automatically <i>related model methods</i>
---
<br clear="all"/>

{% include important.html content="When accessing a related model, the active ACL is still the one for the model you are calling.
So even if your model has DENY ALL permissions set, if the model relating to it has no ACL, then all the relation endpoints will be open. This can be a security risk because, for example, `GET /OpenModel/{id}/ACLSecuredModel` will allow full access to `ACLSecuredModel` through the `OpenModel` relations.
{% include important.html content="When accessing a related model, the effective ACL (the one considered to resolve access permission) is the one for the model you are primarily calling.
So if a given model has no ACL, then all the endpoints accessible through the model relations will be open, even if a related model has DENY ALL permissions set.

This can be a security risk since, for example, `GET /OpenModel/{id}/ACLSecuredModel` will allow full access to `ACLSecuredModel` through the `OpenModel` relations.
" %}

## Restricting access to related models
Expand Down
236 changes: 217 additions & 19 deletions pages/en/lb3/Authentication-authorization-and-permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: "Authentication, authorization, and permissions"
lang: en
layout: navgroup
toc_level: 2
navgroup: user-mgmt
keywords: LoopBack
tags: authentication
Expand All @@ -20,21 +21,11 @@ LoopBack apps access data through models (see [Defining models](Defining-models
so controlling access to data means putting restrictions on models; that is,
specifying who or what can read/write the data or execute methods on the models. 

When you create your app with the LoopBack [application generator](Application-generator.html), access control is automatically enabled, _except_ if you choose the "empty-server" application type.
To enable access control for an "empty-server" application, you must add a boot
script that calls `enableAuth()`. For example, in `server/boot/authentication.js`:

```js
module.exports = function enableAuthentication(server) {
server.enableAuth();
};
```

## Access control concepts

LoopBack's access control system is built around a few core concepts, as summarized in the following table. 

| Term | Description | Responsibility | Example |
| Term&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | Description | Responsibility | Example |
|---|---|---|---|
| Principal | An entity that can be identified or authenticated. | Represents identities of a request to protected resources. | A user <br/> An application <br/> A role (please note a role is also a principal) |
| Role | A group of principals with the same permissions. | Organizes principals into groups so they can be used. | **Dynamic role**: <br/>`$everyone` (for all users) <br/>`$unauthenticated` (unauthenticated users) <br/> `$owner` (the principal is owner of the model instance), which can be:<br/>&nbsp;&nbsp;&#9702; A simple property called `userId`<br/>&nbsp;&nbsp;&#9702; A simple property called `owner`<br/>&nbsp;&nbsp;&#9702; A relation to a model that extends User. <br/><br/> **Static role**: admin (a defined role for administrators) |
Expand All @@ -45,15 +36,222 @@ LoopBack's access control system is built around a few core concepts, as summari

The general process to implement access control for an application is:

1. **Specify user roles**.
1. **Implement authentication**:
In the application, add code to create (register) new users, log in users (get and use authentication tokens), and log out users.
2. **Specify user roles**.
Define the user roles that your application requires.
For example, you might create roles for anonymous users, authorized users, and administrators. 
2. **Define access for each role and model method**.
For example, you might create roles for anonymous users, authenticated users, group members, and administrators. 
3. **Define access for each role and model method**.
For example, you might enable anonymous users to read a list of banks, but not allow them to do anything else.
LoopBack models have a set of built-in methods, and each method maps to either the READ or WRITE access type.
In essence, this step amounts to specifying whether access is allowed for each role and each Model - access type, as illustrated in the example below.
3. **Implement authentication**:
in the application, add code to create (register) new users, login users (get and use authentication tokens), and logout users.
In essence, this step amounts to specifying whether access is allowed for each role and each Model/access type, as illustrated in the example below.<br/><br/>
NOTE: you can also map access rights directly to specific users or applications.
4. **Set-up access control for users**. Do one of:
- Statically map users with any role previously created using the Role model. For more information, see [Static roles](Defining-and-using-roles.html#static-roles).
- Add code to register dynamic role resolvers that at runtime resolve whether a user, given a preconfigured set of conditions, has a given role. For more information, see [Dynamic roles](Defining-and-using-roles.html#dynamic-roles).

## Initial setup

### Enabling access control

Applications created with the LoopBack [application generator](Application-generator.html)
have access control enabled by default, _except_ for the "empty-server" application type.
To enable access control for an "empty-server" application, add a boot
script that calls [`enableAuth()`](https://apidocs.strongloop.com/loopback/#app-enableauth); for example:

{% include code-caption.html content="server/boot/authentication.js" %}
```javascript
module.exports = function enableAuthentication(server) {
server.enableAuth();
};
```

### Preparing access control models

Make sure the `User` model, (and possibly the `AccessToken` model) is configured appropriately according to your set-up.

Normally you should have already implemented at least one custom user model, extending the built-in `User` model, as described in [Using built-in models](Using-built-in-models.html#user-model).

Usually you don't need to extend or customize the built-in models `Role`, `RoleMapping`, and `ACL`. Just make sure they are declared in the `model-config.json` configuration file, or in case you don't require either to customize the `AccessToken` model that you pass a datasource to the `enableAuth()` method as follows:

```javascript
server.enableAuth({ datasource: 'db' });
```

{% include note.html content="
Passing a `datasource` to the `enableAuth()` method as shown here will let LoopBack take care of attaching any built-in models required by the access control feature, which is suitable for most applications.
" %}

{% include tip.html content="
Whether you can use the built-in `AccessToken` model or create a custom accessToken model extending the built-in model depends on whether you plan to use one or several user models extending the built-in `User` model. Both cases are covered in the next two sections.
" %}

#### Access control with a single user model

If your application leverages only one type of user extending the built-in `User` model (which should the case in a majority of configurations), you have little further configuration to do.

1. Rely on the built-in `AccessToken` model.
2. Make sure your custom user model implements a hasMany relation with the AccessToken model as follows:

{% include code-caption.html content="common/models/custom-user.json" %}
```json
...
"relations": {
"accessTokens": {
"type": "hasMany",
"model": "AccessToken",
"foreignKey": "userId",
"options": {
"disableInclude": true
}
}
},
...
```

#### Access control with multiple user models

Having multiple user models may be required in certain situations to deal with significantly different types of users in an application.

If the types of users differ by just a few properties, then it's easiest to overload a single custom user model with all properties required, and differentiate access control behavior with static roles mapped to the different user types.

A more complex situation is when the different types of users differ not just in their properties, but also by their access rights and relations with other models. For example, applications where the concept of an _organization_ is involved, creating intertwined layers of relationships and thus of access control.
Such circumstances require the application to manage the different user types in separate models.

Consider the following example:

- **Application** has `Users`: _app-admins, app-managers, app-auditors, etc._
- **Application** has Organizations
- **Organizations** have `Users`: _org-admins, org-managers, org-marketing, org-sales_
- **Organizations** have Customers (which are also `Users`)

Such an application has three different types of users:

- `App-Managers`
- `Org-Managers`
- `Org-Customers`

Each type of user has different relations with and access rights to the models composing the application.

##### Setup

{% include important.html content="
When using multiple user models, you should not let LoopBack auto-attach built-in models required by the access control feature. Instead, call the `enableAuth()` method with no argument and manually define all models required in the `server/model-config.json` configuration file.
" %}

To use several models extending the built-in `User` model, you must modify the relations between the `users` models and the `AccessToken` models to allow a single `AccessToken` model to host access tokens for multiple types of users while at the same time allowing each `user` model instance to be linked to unique related access tokens.

This is achieved by changing the **hasMany** relation from `User` to `AccessToken` and the **belongsTo** relation from `AccessToken` to `User` by their [polymorphic](Polymorphic-relations.html) equivalents, in which the `principalType` property is used as a _discriminator_ to resolve which of the potential `user` model instance an 'accessToken' instance belongs to.

{% include note.html content="Adapt the following configuration snippets in your custom `users` and `accessToken` model definitions.
"%}

{% include code-caption.html content="common/models/any-custom-user.json" %}
```json
...
"relations": {
"accessTokens": {
"type": "hasMany",
"model": "customAccessToken",
"polymorphic": {
"foreignKey": "userId",
"discriminator": "principalType"
},
"options": {
"disableInclude": true
}
}
},
...
```

{% include code-caption.html content="common/models/custom-access-token.json" %}
```json
...
"relations": {
"user": {
"type": "belongsTo",
"idName": "id",
"polymorphic": {
"idType": "string",
"foreignKey": "userId",
"discriminator": "principalType"
}
}
},
...
```

{% include important.html content="
In particular, pay attention to:

* The `model` name used to refer to the access token model in the different user models (here named \"customAccessToken\")
* The `idName` used for the foreignKey in the access token model referring to the user instance (here named \"id\")
* The `idType` used for this foreignKey, according to the type of connector used for the related user models (here using \"string\" for a MongoDB connector for example)
* Use \"principalType\" for the `discriminator` name. This is mandatory and cannot be changed
" %}

Don't forget to specify the custom `accessToken` model as follows:

{% include code-caption.html content="server/middleware.json" %}
```json
{
"auth": {
"loopback#token": {
"params": {
"model": "customAccessToken"
}
}
}
}
```

**Note**: Alternatively, you can put these lines in the `server.js` file or in a boot script, once again paying attention to the _name_ of the custom `accessToken` model.

{% include code-caption.html content="server/server.js" %}
```javascript
var loopback = require('loopback');
...
app.use(loopback.token({
model: app.models.customAccessToken
}));
Copy link
Member

@bajtos bajtos Mar 1, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idiomatic way is to configure middleware in server/middleware.json.

See https://github.com/strongloop/loopback-workspace/blob/fc4f761ed7b635cf18e091b6ad111c31c78880a9/templates/projects/empty-server/files/server/middleware.json#L34

{
  "auth": {
    "loopback#token": {
      "params": {
        "model": "customAccessToken"
      }
    }
  }
}

(I typed the example from my head, may not work out of the box.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bajtos I updated as per your snippet. I'd appreciate if you can link me to the exact reference, or assert this one is good

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this doc page is the most relevant: https://docs.strongloop.com/display/public/LB/Defining+middleware

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new content showing loopback#token configuration looks good to me.

```

{% include tip.html content="From this point you should be able to use LoopBack access control over multiple user models extending the built-in `User` model, with barely no modification compared to the way you're used to handle it with a single user model.
" %}

##### Methods and parameters impacted when using multiple user models

{% include important.html content="
Pay attention to the following methods and parameters impacted by the switch to multiple user models support.
" %}

Anytime a method is expecting the **principalType** for a principal of the `User` type (as-is or nested in an [AccessContext](https://apidocs.strongloop.com/loopback/#accesscontext) object), provide the name of the targeted `user` model name (e.g. `'oneCustomUserModelName'`) instead of the usual `Principal.USER` (or `'USER'`).<br>
Such methods include: `Role.getRoles()` and `Role.isInRole()`. For example:

```javascript
Role.getRoles({
principalType: 'oneCustomUserModelName',
principalId: 123,
});
```

`Role` instance method `Role.prototype.users()`: the method which return all the users mapped with a given role instance should now be called with the following syntax:

```javascript
roleInstance.users({where: {
principalType: 'oneCustomUserModelName'
});
```

`RoleMapping` static methods: these methods either accessed directly or through the relation `principals` of the `Role` model should also use the new `principalType` syntax, for example:

```javascript
roleInstance.principals.create({
principalType: 'oneCustomUserModelName',
principalId: 123
});
```

## Exposing and hiding models, methods, and endpoints

Expand Down Expand Up @@ -124,9 +322,9 @@ MyUser.disableRemoteMethod('__get__accessTokens', false);
MyUser.disableRemoteMethod('__updateById__accessTokens', false);
```

### Read-Only endpoints example
### Read-only endpoints example

You may want to only expose read-only operations on your model hiding all POST, PUT, DELETE verbs
You may want to only expose read-only operations on your model; in other words hiding all operations that use HTTP POST, PUT, DELETE method.

**common/models/model.js**

Expand Down
28 changes: 13 additions & 15 deletions pages/en/lb3/Controlling-data-access.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,21 +161,19 @@ Each incoming request is mapped to an object with three attributes:
ACL rules are described as an array of objects, each of which consists of attributes listed at 
[Model definition JSON file - ACLs](Model-definition-JSON-file.html#acls). 

1. model
2. property
3. accessType
4. principalType
1. USER
2. APP
3. ROLE
1. custom roles
2. $owner
3. $authenticated
4. $unauthenticated
5. $everyone
5. permission
1. DENY
2. ALLOW
* model
* property
* accessType
* principalType
* USER: a user ID
* APP: an application ID
* ROLE: a role name
* _built-in dynamic roles_, one of [`$everyone`, `$unauthenticated`, `$authenticated`, `$owner`]
* [_custom static roles_](Defining-and-using-roles.html#static-roles), directly mapped to principals
* [_custom dynamic roles_](Defining-and-using-roles.html#dynamic-roles), registered with custom role resolvers
* permission
* DENY
* ALLOW

### ACL rule precedence

Expand Down
14 changes: 10 additions & 4 deletions pages/en/lb3/Model-definition-JSON-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,7 @@ The value of the `acls` key is an array of objects that describes the access
<table>
<thead>
<tr>
<th>Key</th>
<th width="120">Key</th>
<th>Type</th>
<th>Description</th>
</tr>
Expand Down Expand Up @@ -783,17 +783,23 @@ The value of the `acls` key is an array of objects that describes the access
The value must be one of:
<ul>
<li>A user ID (String|number|any)</li>
<li>An application ID (String|number|any)</li>
<li>
One of the following predefined dynamic roles:
<ul>
<li><code>$everyone</code>&nbsp;- Everyone</li>
<li><code>$owner</code>&nbsp;- Owner of the object</li>
<li><code>$related</code>&nbsp;- Any user with a relationship to the object (not implemented yet!)</li>
<li><code>$authenticated</code>&nbsp;- Authenticated user</li>
<li><code>$unauthenticated</code>&nbsp;- Unauthenticated user</li>
</ul>
</li>
<li>A static role name</li>
<li>
A custom role name, either:
<ul>
<li><b>static</b>, directly mapped to a principal</li>
<li><b>dynamic</b>, registered with a custom role resolver</li>
</ul>
</li>
</ul>
</td>
</tr>
Expand All @@ -804,7 +810,7 @@ The value of the `acls` key is an array of objects that describes the access
Type of the principal. Required.
One of:
<ul>
<li>APPLICATION</li>
<li>APP</li>
<li>USER</li>
<li>ROLE</li>
</ul>
Expand Down
4 changes: 2 additions & 2 deletions pages/en/lb3/Using-built-in-models.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ summary:

## Overview

Loopback provides useful built-in models for common use cases:
LoopBack provides useful built-in models for common use cases:

* **[Application model](#application-model)** - contains metadata for a client application that has its own identity and associated configuration with the LoopBack server.
* **[User model](#user-model)** - register and authenticate users of your app locally or against third-party services.
Expand Down Expand Up @@ -40,7 +40,7 @@ The default model definition file is [common/models/user.json](https://github.c
{% include important.html content="
You must create your own custom model (named something other than \"User,\" for example \"Customer\" or \"Client\") that [extends the built-in User model](Extending-built-in-models.html) rather than use the built-in User model directly. The built-in User model provides a great deal of commonly-used functionality that you can use via your custom model.

LoopBack does not support multiple models based on the User model in a single application. That is, you cannot have more than one model derived from the built-in User model in a single app.
Starting with version 3.3.0, LoopBack supports applications with multiple models based on the User model. For more information, see [Access control with multiple user models](Authentication-authorization-and-permissions.html#access-control-with-multiple-user-models).
" %}

For more information, see [Managing users](Managing-users.html).
Expand Down