LoopBack は組み込みのトークンに基づく認証が含まれています。
Page Contents

多くのアプリケーションは、誰(何)がデータにアクセスしたりサービスを呼出したりできるのかを制御する必要があります。 一般的に、これは保護されたデータにアクセスするために、ユーザにログインを求めたり、アプリケーションに認証トークンを求めたりすることを含みます。

LoopBackのアクセス制御の実装に関する単純な例は、GitHubの loopback-example-access-control リポジトリを参照してください。

LoopBack アプリケーションはモデル(モデルの定義を参照)を通じてデータにアクセスするため、 データへのアクセス制御は、モデルに制限を加えることを意味します。 つまり、誰または何がデータを読み書きできるのか、モデルのメソッドを実行できるのか指定する、ということです。

アクセス制御の概念

LoopBackのアクセス制御システムは、幾つかの核となる概念から構成されています。以下の表にまとめます。

用語        説明 責任
プリンシパル 識別されたり認証されたりできる実体 保護されたリソースへの要求のIDを表します。 ユーザー
アプリケーション
ロール (ロールもまたプリンシパルであることに注意してください)
ロール 同じパーミッションを持つプリンシパルのグループ プリンシパルを扱いやすくグループにまとめる。 動的ロール:
$everyone (すべてのユーザー)
$unauthenticated (認証されていないユーザー)
$owner (モデルのインスタンスを所有するプリンシパル), 可能ならば、
  ◦ userId と呼ばれる単純なID
  ◦ owner と呼ばれる単純なプロパティ
  ◦ Userを拡張したモデルとの関係

静的ロール: 管理者 (管理者向けに定義されたロール)
ロールマッピング ロールに割り当てたプリンシパル ロールに静的に割り当てたプリンシパル ID 1 のユーザをロール 1 に割り当てる
「管理者」ロールをロール 1 に割り当てる
ACL アクセス制御リスト プリンシパルがモデルに対して幾つかの操作を行えるか制御するもの project モデルに対する全ユーザのアクセスを拒否する
「管理者」ロールに、project モデルに対する find() メソッドの実行を許可する

一般的な手続き

アプリケーションにアクセス制御を実装する一般的な手続きは以下のとおりです。

  1. 認証の実装 アプリケーションにおいて、新しいユーザーを作成(登録)し、ユーザーがログイン(認証トークンを取得・使用)し、ユーザがログアウトするコードを追加します。
  2. ユーザーロールの設定 アプリケーションが必要とするユーザーのロールを定義します。 例えば、匿名ユーザー・認証済みユーザー・グループメンバー・管理者のようなロールを作るかもしれません。
  3. 各ロール・モデルのメソッドについて、アクセス可能かどうかを決める. 例えば、匿名ユーザーには銀行のリストを読み取ることを許可して、他はすべて禁止するかもしれません。 LoopBackのモデルは、組み込みのメソッド一式を持っており、それぞれのメソッドは、読み取りまたは書き込みのどちらかのアクセス種別に当たります。 本質的には、このステップでは各ロールと各モデルのアクセス種別ごとに、どのアクセスが許可されるかを指定する事になります。後ほど例を示します。

    注:アクセス権を特定のユーザーやアプリケーションに直接割り当てることもできます。
  4. ユーザーのアクセス制御をセットアップします. 以下のいずれか一つを実施します。
    • ロールモデルを使って事前に作成したロールに、ユーザを静的に割り当てる。詳細は、静的ロールを参照。
    • 事前に設定された条件に基づいて、ユーザがロールを持つかどうかを実行時に解決する、動的ロールリゾルバを登録するコードを追加する。詳細は、動的ロールを参照。

初期セットアップ

アクセス制御を有効にする

LoopBackのアプリケーション生成ツールで作成したアプリケーションは、 アプリケーション種別に「空のサーバ」を選んだ場合を 除き 、既定でアクセス制御が有効になっています。 「空のサーバ」アプリケーションで、アクセス制御を有効にするには、起動スクリプトで enableAuth()を呼び出すようにします。

server/boot/authentication.js

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

アクセス制御モデルを準備する

セットアップするには、Userモデルを(そしておそらくはAccessTokenモデルも)設定しなければなりません。

ベストプラクティスは、組み込みのUserモデルをそのまま使うのではなく、組み込みモデルの利用 にあるように、 それを拡張した独自ユーザモデルを少なくとも1つ実装することです。

通常、組み込みのRoleRoleMappingACLモデルを拡張したりカスタマイズしたりする必要はありません。model-config.json 設定ファイルで それらが宣言されていることを確認してください。AccessTokenモデルをカスタマイズする必要がなければ、enableAuth()メソッドにデータソースを渡してください。

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

単一ユーザモデルによるアクセス制御

最も一般的なシナリオでは、アプリケーションは組み込みのUserモデルを拡張したモデルをひとつだけと、組み込みのAccessTokenモデルを使います。 この場合、組み込みAccessTokenモデルの「belongsTo」関係を、独自のユーザモデルを参照するように変更する必要があります。 これを行うには、以下のようにserver/model-config.jsonファイルを編集してください。

server/model-config.json

{
  // ...
  "AccessToken": {
    "dataSource": "db",
    "public": false,
    "relations": {
      "user": {
        "type": "belongsTo",
        "model": "user",
        "foreignKey": "userId"
      }
    }
  }
  // ...
}
AccessTokenモデルのカスタマイズ

AccessTokenモデルをカスタマイズする必要がある場合、例えば追加のプロパティを増やしたいときなどは、 認証のために新しいAccessTokenモデルを使うようにUserモデルを変更する必要があります。

独自のUserモデル定義ファイルにおいて、relationsセクションの「accessTokens」関係が独自のAccessTokenモデルを使うように設定してください。

common/models/custom-user.json

{
  "name": "CustomUser",
  "base": "User",
  // ...
  "relations": {
    "accessTokens": {
      "type": "hasMany",
      "model": "AccessToken",
      "foreignKey": "userId",
      "options": {
        "disableInclude": true
      }
    }
  },
  // ...
}

複数のユーザーモデルを使ったアクセス制御

全く異なるタイプのユーザーがいるアプリケーションでは、複数のユーザーモデルを必要とするかもしれません。

ユーザーの違いが、幾つかのプロパティにとどまるのであれば、必要なプロパティを全て持つ単一の独自ユーザーモデルを上書きして、ユーザー種別ごとに静的ロールを 割り当ててアクセス制御の振る舞いの違いを表すのが最も簡単な方法です。

しかし、異なるアクセス権や他のモデルとの関係が異なっていたり、それぞれのプロパティがかけ離れている場合など、複数の独立したユーザーモデルが必要になるかもしれません。たとえば、組織の概念が関係するアプリケーションでは、関係の絡み合った階層やアクセス制御を作成するでしょう。

以下のような例を考えます。

  • アプリケーション には複数のUsersが属する。app-admins, app-managers, app-auditors, など
  • アプリケーション には複数の組織が属する。
  • 組織 には複数の Users が属する。 org-admins, org-managers, org-marketing, org-sales
  • 組織 には複数の顧客 (Usersでもある) が属する。

このようなアプリケーションでは3つの異なるユーザー種別が必要です。

  • App-Managers
  • Org-Managers
  • Org-Customers

それぞれのユーザー種別は、アプリケーションを構成するモデルと異なる関係を持ち、異なるアクセス権を持ちます。

セットアップ

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 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. In addition to having custom user models this requires you also define a custom AccessToken model extending the built-in AccessToken model.

common/models/any-custom-user.json

{
  "name": "AnyCustomUser",
  "base": "User",
  // ..
  "relations": {
    "accessTokens": {
      "type": "hasMany",
      "model": "CustomAccessToken",
      "polymorphic": {
        "foreignKey": "userId",
        "discriminator": "principalType"
      },
      "options": {
        "disableInclude": true
      }
    }
  },
  // ..
}
...

common/models/custom-access-token.json

{
  "name": "CustomAccessToken",
  "base": "AccessToken",
  // ..
  "relations": {
    "user": {
    "type": "belongsTo",
      "idName": "id",
      "polymorphic": {
        "idType": "string",
        "foreignKey": "userId",
        "discriminator": "principalType"
      }
    }
  },
  // ...
}

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

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

server/server.js

var loopback = require('loopback');
...
app.use(loopback.token({
  model: app.models.CustomAccessToken
}));
Methods and parameters impacted when using multiple user models

Anytime a method is expecting the principalType for a principal of the User type (as-is or nested in an AccessContext object), provide the name of the targeted user model name (e.g. 'oneCustomUserModelName') instead of the usual Principal.USER (or 'USER').
Such methods include: Role.getRoles() and Role.isInRole(). For example:

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:

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:

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

Exposing and hiding models, methods, and endpoints

To expose a model over REST, set the public property to true in /server/model-config.json:

...
  "Role": {
    "dataSource": "db",
    "public": false
  },
...

Hiding methods and REST endpoints

If you don’t want to expose certain create, retrieve, update, and delete operations, you can easily hide them by calling  disableRemoteMethodByName() on the model.  For example, following the previous example, by convention custom model code would go in the file common/models/location.js. You would add the following lines to “hide” one of the predefined remote methods:

common/models/location.js

MyModel.disableRemoteMethodByName('deleteById');

Now the deleteById() operation and the corresponding REST endpoint will not be publicly available.

For a method on the prototype object, such as updateAttributes():

common/models/location.js

MyModel.disableRemoteMethod('prototype.updateAttributes');

Here’s an example of hiding all methods of the MyUser model, except for login and logout. It assumes MyUser is an extended built-in User model:

MyUser.disableRemoteMethodByName("upsert");                               // disables PATCH /MyUsers
MyUser.disableRemoteMethodByName("find");                                 // disables GET /MyUsers
MyUser.disableRemoteMethodByName("replaceOrCreate");                      // disables PUT /MyUsers
MyUser.disableRemoteMethodByName("create");                               // disables POST /MyUsers

MyUser.disableRemoteMethodByName("prototype.updateAttributes");           // disables PATCH /MyUsers/{id}
MyUser.disableRemoteMethodByName("findById");                             // disables GET /MyUsers/{id}
MyUser.disableRemoteMethodByName("exists");                               // disables HEAD /MyUsers/{id}
MyUser.disableRemoteMethodByName("replaceById");                          // disables PUT /MyUsers/{id}
MyUser.disableRemoteMethodByName("deleteById");                           // disables DELETE /MyUsers/{id}

MyUser.disableRemoteMethodByName('prototype.__get__accessTokens');        // disable GET /MyUsers/{id}/accessTokens
MyUser.disableRemoteMethodByName('prototype.__create__accessTokens');     // disable POST /MyUsers/{id}/accessTokens
MyUser.disableRemoteMethodByName('prototype.__delete__accessTokens');     // disable DELETE /MyUsers/{id}/accessTokens

MyUser.disableRemoteMethodByName('prototype.__findById__accessTokens');   // disable GET /MyUsers/{id}/accessTokens/{fk}
MyUser.disableRemoteMethodByName('prototype.__updateById__accessTokens'); // disable PUT /MyUsers/{id}/accessTokens/{fk}
MyUser.disableRemoteMethodByName('prototype.__destroyById__accessTokens');// disable DELETE /MyUsers/{id}/accessTokens/{fk}

MyUser.disableRemoteMethodByName('prototype.__count__accessTokens');      // disable  GET /MyUsers/{id}/accessTokens/count

MyUser.disableRemoteMethodByName("prototype.verify");                     // disable POST /MyUsers/{id}/verify
MyUser.disableRemoteMethodByName("changePassword");                       // disable POST /MyUsers/change-password
MyUser.disableRemoteMethodByName("createChangeStream");                   // disable GET and POST /MyUsers/change-stream

MyUser.disableRemoteMethodByName("confirm");                              // disables GET /MyUsers/confirm
MyUser.disableRemoteMethodByName("count");                                // disables GET /MyUsers/count
MyUser.disableRemoteMethodByName("findOne");                              // disables GET /MyUsers/findOne

//MyUser.disableRemoteMethodByName("login");                                // disables POST /MyUsers/login
//MyUser.disableRemoteMethodByName("logout");                               // disables POST /MyUsers/logout

MyUser.disableRemoteMethodByName("resetPassword");                        // disables POST /MyUsers/reset
MyUser.disableRemoteMethodByName("setPassword");                          // disables POST /MyUsers/reset-password
MyUser.disableRemoteMethodByName("update");                               // disables POST /MyUsers/update
MyUser.disableRemoteMethodByName("upsertWithWhere");                      // disables POST /MyUsers/upsertWithWhere

Read-only endpoints example

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

Product.disableRemoteMethodByName('create');		// Removes (POST) /products
Product.disableRemoteMethodByName('upsert');		// Removes (PUT) /products
Product.disableRemoteMethodByName('deleteById');	// Removes (DELETE) /products/:id
Product.disableRemoteMethodByName("updateAll");		// Removes (POST) /products/update
Product.disableRemoteMethodByName("prototype.updateAttributes"); // Removes (PUT) /products/:id
Product.disableRemoteMethodByName("prototype.patchAttributes");  // Removes (PATCH) /products/:id
Product.disableRemoteMethodByName('createChangeStream'); // Removes (GET|POST) /products/change-stream

To disable REST endpoints for related model methods, use disableRemoteMethodByName().

For example, if there are post and tag models, where a post hasMany tags, add the following code to /common/models/post.js  to disable the remote methods for the related model and the corresponding REST endpoints: 

common/models/model.js

module.exports = function(Post) {
  Post.disableRemoteMethodByName('prototype.__get__tags');
  Post.disableRemoteMethodByName('prototype.__create__tags');
  Post.disableRemoteMethodByName('prototype.__destroyById__accessTokens'); // DELETE
  Post.disableRemoteMethodByName('prototype.__updateById__accessTokens'); // PUT
};

Hiding properties

To hide a property of a model exposed over REST, define a hidden property. See Model definition JSON file (Hidden properties).