はじめに
この記事はHTTP APIへのアクセスをRBAC(Role Based Access Control)で制御する手順で、こちらの原文を元に作成しています。nodeとnpmのインストール、Auth0の無料アカウントの取得とテナントの作成が完了していることが前提となっています。まだの方はこちらの記事を参照の上ご準備をお願いします。
環境
-
OS :
macOS Mojave 10.14.6
-
node :
10.15.3
-
npm :
6.11.3
手順
APIの登録
Auth0ダッシュボードの左ペインで"APIs"をクリック、右上の"CREATE API"を押します。
休暇申請を行うAPIを想定しています。"Name"に"Vacation API" "Identifier"に"https://vacation-api.troubleshoo.com
"を入力、"Signing Algorithmはデフォルトのまま"CREATE"を押します。Identifierは実在するURLである必要はありません。管理し易い任意の名前を入力します。
"Permissions"タブをクリック、左のテキストボックスに"create:vacations"、右のテキストボックスに"Create vacations requests."を入力して"ADD"を押します。APIのScope(Permission)を定義しています。左のテキストボックスに入力するScopeの書式はxx:xxである必要があります。
同様に"read:vacations"、"Read vacation requests."を入力して"ADD"を押します。
"Settings"タブをクリックして"Enable RBAC"、"Add Permissions in the Access Token"フリップスイッチをオンにして"SAVE"を押します。Auth0のRBAC機能を有効化して、ユーザが認証されたタイミングでaccess_tokenに認可されたAPIのScopeを含めます。
同様にExpense API, Invoice APIを作成します。
Roleの作成
左ペインの"Users&Roles"->Roles"をクリック、"CREATE ROLE"を押します。
"Name"に"Vacation Requester"、"Description"に"Role that gives users permissions to request vacations."を入力して"CREATE"を押します。
"Permissions"タブをクリックして"ADD PERMISSIONS"をクリックします。
"Vacation API"を選択して"Select All"をクリック、"ADD PERMISSIONS"を押します。RoleにAPIのScopeを紐付けしています。
同様にExpense Submitter, Invoice Submitterを作成します。
Userの作成とRoleの割り当て
左ペインの"Users&Roles"->Users"をクリック、"CREATE USER"を押します。Email/Passwordを入力して"CREATE"を押します。
"Roles"タブをクリックして"ASSIGN ROLES"を押します。
"Vacation Requester", "Expense Submitter"を選択して"ASSIGN"を押します。UserにRoleを割り当てています。
同様に別のユーザを作成して"Expense Submitter", "Invoice Submitter"をアサインします。
Applicationの登録
左ペインの"Applications"をクリックして右上の"CREATE APPLICATION"を押します。
任意の名前を入力して"Choose an application type"で"Single Page Web Applications"を選択して"CREATE"を押します。
"Settings"タブをクリック、"Allowed Callback URLs"に"https://troubleshoo.now.sh/callback
", "Allowed Web Origins"と"Allowed Logout URLS"に"https://troubleshoo.now.sh
"を入力して"SAVE CHANGES"を押します。Auth0で認証後にリダイレクトできるURLを申告しています。
API/Applicationの実装
API/ApplicationのGitHubリポジトリをCloneします。
$ git clone git clone https://github.com/auth0-blog/rbac-nodejs.git
PackageをインストールしてAPIとApplicationを起動します。
$ cd rbac-nodejs
$ npm install
$ npm start
APIにリクエストを投げます。Auth0のRBAC機能で保護していないためリクエストが成功することを確認しています。
$ curl http://localhost:3001/vacations
"GET /vacations HTTP/1.1" 200 2 "-" "curl/7.54.0"
rbac-nodejs/srcにauthorizationUtils.jsを作成します。access_tokenとpermissionをチェックするFunctionを実装しています。
const jwt = require('express-jwt');
const jwksRsa = require('jwks-rsa');
function checkAccessToken(issuer, audience) {
return jwt({
secret: jwksRsa.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `https://${issuer}/.well-known/jwks.json`
}),
// Validate the audience and the issuer.
audience,
issuer: `https://${issuer}/`,
algorithms: ['RS256']
})
}
function checkPermission(permission) {
return (req, res, next) => {
const {permissions} = req.user;
if (permissions.includes(permission)) return next();
res.status(403).send();
}
}
module.exports = {
checkAccessToken,
checkPermission,
};
rbac-nodejs/src/apisのinvoiceAPI.js, expensesAPI.js, vacationAPI.jsを修正します。Auth0のRBAC機能で保護しています。
const express = require('express');
const {checkAccessToken, checkPermission} = require('../authorizationUtils');
const {insertInvoice, getInvoices} = require('../database/invoices');
const router = express.Router();
router.use(checkAccessToken(process.env.ISSUER, process.env.INVOICE_API));
// endpoint to return all invoice requests
router.get('/', checkPermission('read:invoices'), async (req, res) => {
res.send(await getInvoices());
});
// endpoint to insert new invoice request
router.post('/', checkPermission('create:invoice'), async (req, res) => {
const newInvoice = req.body;
await insertInvoice(newInvoice);
res.send({ message: 'Invoice request inserted.' });
});
module.exports = router;
const express = require('express');
const {checkAccessToken, checkPermission} = require('../authorizationUtils');
const {insertExpense, getExpenses} = require('../database/expenses');
const router = express.Router();
router.use(checkAccessToken(process.env.ISSUER, process.env.EXPENSE_API));
// endpoint to return all expense reports
router.get('/', checkPermission('read:expenses'), async (req, res) => {
res.send(await getExpenses());
});
// endpoint to insert new expense report
router.post('/', checkPermission('create:expenses'), async (req, res) => {
const newExpense = req.body;
await insertExpense(newExpense);
res.send({ message: 'Expense report inserted.' });
});
module.exports = router;
const express = require('express');
const {checkAccessToken, checkPermission} = require('../authorizationUtils');
const {insertVacation, getVacations} = require('../database/vacations');
const router = express.Router();
router.use(checkAccessToken(process.env.ISSUER, process.env.VACATION_API));
// endpoint to return all vacation requests
router.get('/', checkPermission('read:vacations'), async (req, res) => {
res.send(await getVacations());
});
// endpoint to insert new vacation request
router.post('/', checkPermission('create:vacations'), async (req, res) => {
const newVacation = req.body;
await insertVacation(newVacation);
res.send({ message: 'Vacation request inserted.' });
});
module.exports = router;
rbac-nodejsに.envを作成します。ISSUERはAuth0のテナント名、xxx_APIは各々のAPI Identifierを指定します。
ISSUER=kiriko.auth0.co
EXPENSE_API=https://expense-api.troubleshoo.com
INVOICE_API=https://invoice-api.troubleshoo.com
VACATION_API=https://vacation-api.troubleshoo.com
APIとApplicationを起動してChromeでhttps://https://troubleshoo.now.sh
にアクセスします。
$ cd rbac-nodejs
$ npm start
パラメータを設定して"SAVE"を押します。"Auth0 Client ID"はAuth0ダッシュボードで登録したApplicationのClient IDを指定します。
おわりに
従来型のRBACの実装は、プロジェクト毎にRDBMSにUser-Group-Role-Permissionの紐付けを作成するケースが多いのではないでしょうか。開発者は要件が変わる度にテーブルスキーマを変更して、テストして、バグ改修してようやくリリース、リリース後のインシデント対応、HotFix作成、また要件が変わってテーブルスキーマ再設計。。。とRBACの運用と改修対応に追われてしまい本来やるべき活動(ソフトウェアで社会を変革する)ができず苦慮されていると思います。RBACはAuth0に任せて本来やるべきことに集中頂きたいですね。