Help us understand the problem. What is going on with this article?

RESTful APIでAuth0 Management APIのWrapperを作成する

はじめに

Auth0はManagement APIを公開していてダッシュボードで出来る管理作業(例/API Clientの作成や削除)はManagement APIでも可能です。この記事はManagement API用のWrapperをRESTful APIで作成する手順(下のダイアグラムの点線内を作成します)で、こちらの原文を元に作成しています。nodeとnpmのインストール、Auth0の無料アカウントの取得とテナントの作成が完了していることが前提となっています。まだの方はこちらの記事を参照の上ご準備をお願いします。

完成版のソースコードはこちらにあります。

環境

  • OS : macOS Mojave 10.14.6
  • node : 10.15.3
  • npm : 6.11.3

手順

プロジェクトの準備

GitHubリポジトリをローカルにクローンします。

$ git clone https://github.com/auth0-blog/auth0-restful-dashboard.git

nodeのパッケージをインストールします。

$ cd auth0-restful-dashboard
$ cd auth0-restful-management-api
$ npm install

WrapperをAuth0のApplicationとして登録

Auth0ダッシュボードの左ペインから"Applications"をクリック、右上の"CREATE APPLICATION"を押します。

"Name"に任意の名前を入力、"Choose an application type"で"Machine to Machine Applications"を選択して"CREATE"を押します。

"Auth0 Management API"を選択、Scopeは"read:clients", "update:clients"を選択して"AUTHORIZE"を押します。

~/auth0-restful-management-api/.envを作成します。

$ cd ~/auth0-restful-management-api
$ vi .env

環境変数を設定します。"CLIENT_DOAMIN"はAuth0のテナント名、"CLIENT_ID", "CLIENT_SECRET"は作成したApplicationをクリックして"Setting"タグから確認できます。

.env
CLIENT_DOMAIN="kiriko.auth0.com"
CLIENT_ID="xxxxxxxx"
CLIENT_SECRET="xxxxxxxx"

RESTful API Gatewayの作成

~/auth0-restful-management-api/index.jsをバックアップして空のindex.jsを作成します。何をどのように何故やるのかを理解するためスクラッチで実装します。

$ cd ~/auth0-restful-management-api
$ cp index.js index.js.org
$ touch index.js

index.jsを作成します。Management APIのRoot EndPointとClient Listを取得するFunctionを実装しています。

index.js
//index.js
const express = require('express');
const Auth0Manager = require('./Auth0Manager');

require('dotenv').config();

const app = express();
const apiPrefix = '/api';

//Define the root endpoint
app.get(apiPrefix, async (req, res) => {
    const clientsState = await getClients();

    res.send(clientsState);
});

app.listen(3001, () => {
    console.log('listening on port 3001');
});

//Use the Auth0 Management API wrapper and build the response with hypermedia 
async function getClients() {
    await Auth0Manager.init();

    const clients = await Auth0Manager.getClients();
    const clientList = clients.map(client => ({
        id: client.client_id,
        name: client.name,
        description: client.description || "",
        links: [{
            uri: `${apiPrefix}/clients/${client.client_id}`,
            rel: "client"
        }]
    }));

    return { resourceType: "client-list", clients: clientList };
}

Root EndPointを起動してリクエストを投げてみます。

$ pwd
~/auth0-restful-management-api
$ node index.js
listening on port 3001
$ curl localhost:3001/api
{
  "resourceType": "client-list",
  "clients": [
    {
      "id": "xxxxxxxx",
      "name": "client hogehoge",
      "description": "",
      "links": [
        {
          "uri": "/api/clients/xxxxxxxx",
          "rel": "client"
        }
      ]
    },
    {
      "id": "xxxxxxxx",
      "name": "client foobar",
      "description": "",
      "links": [
        {
          "uri": "/api/clients/xxxxxxxx",
          "rel": "client"
        }
      ]
    },
    {
      "id": "xxxxxxxx",
      "name": "client mokomoko",
      "description": "",
      "links": [
        {
          "uri": "/api/clients/xxxxxxxx",
          "rel": "client"
        }
      ]
    },
----省略----
}

特定のClientを取得するFunctionを追加します。以下、追加後のindex.jsです。

index.js
//index.js
const express = require('express');
const Auth0Manager = require('./Auth0Manager');

require('dotenv').config();

const app = express();
const apiPrefix = '/api';

//Define the root endpoint
app.get(apiPrefix, async (req, res) => {
    const clientsState = await getClients();

    res.send(clientsState);
});

app.listen(3001, () => {
    console.log('listening on port 3001');
});

//Use the Auth0 Management API wrapper and build the response with hypermedia 
async function getClients() {
    await Auth0Manager.init();

    const clients = await Auth0Manager.getClients();
    const clientList = clients.map(client => ({
        id: client.client_id,
        name: client.name,
        description: client.description || "",
        links: [{
            uri: `${apiPrefix}/clients/${client.client_id}`,
            rel: "client"
        }]
    }));

    return { resourceType: "client-list", clients: clientList };
}

async function getClient(clientId) {
    await Auth0Manager.init();

    const client = await Auth0Manager.getClient(clientId);

    return {
        resourceType: "client",
        id: client.client_id,
        name: client.name,
        description: client.description || "",
        links: [{
            uri: `${apiPrefix}/clients/${client.client_id}`,
            rel: "self"
        },
        {
            uri: `${apiPrefix}/clients/${client.client_id}`,
            rel: "self",
            type: "PUT"
        }]
    };
}

getClient()をAPI EndPointから呼び出せるようにコードを修正します。以下、修正後のindex.jsです。

index.js
//index.js
const express = require('express');
const Auth0Manager = require('./Auth0Manager');

require('dotenv').config();

const app = express();
const apiPrefix = '/api';

//Define the root endpoint
app.get(`${apiPrefix}/clients/:clientId`, async (req, res) => {
    const clientState = await getClient(req.params.clientId);

    res.send(clientState);
});

app.listen(3001, () => {
    console.log('listening on port 3001');
});

//Use the Auth0 Management API wrapper and build the response with hypermedia 
async function getClients() {
    await Auth0Manager.init();

    const clients = await Auth0Manager.getClients();
    const clientList = clients.map(client => ({
        id: client.client_id,
        name: client.name,
        description: client.description || "",
        links: [{
            uri: `${apiPrefix}/clients/${client.client_id}`,
            rel: "client"
        }]
    }));

    return { resourceType: "client-list", clients: clientList };
}

async function getClient(clientId) {
    await Auth0Manager.init();

    const client = await Auth0Manager.getClient(clientId);

    return {
        resourceType: "client",
        id: client.client_id,
        name: client.name,
        description: client.description || "",
        links: [{
            uri: `${apiPrefix}/clients/${client.client_id}`,
            rel: "self"
        },
        {
            uri: `${apiPrefix}/clients/${client.client_id}`,
            rel: "self",
            type: "PUT"
        }]
    };
}

ClientをUpdateする処理を追加します。以下、追加後のindex.jsです。

index.js
//index.js
const express = require('express');
const Auth0Manager = require('./Auth0Manager');
const bodyParser = require('body-parser');  //import the body-parser library

require('dotenv').config();

const app = express();
const apiPrefix = '/api';

app.use(bodyParser.json());         // enable the body-parser middleware

//Define the root endpoint
app.get(`${apiPrefix}/clients/:clientId`, async (req, res) => {
    const clientState = await getClient(req.params.clientId);

    res.send(clientState);
});

app.listen(3001, () => {
    console.log('listening on port 3001');
});

//Use the Auth0 Management API wrapper and build the response with hypermedia 
async function getClients() {
    await Auth0Manager.init();

    const clients = await Auth0Manager.getClients();
    const clientList = clients.map(client => ({
        id: client.client_id,
        name: client.name,
        description: client.description || "",
        links: [{
            uri: `${apiPrefix}/clients/${client.client_id}`,
            rel: "client"
        }]
    }));

    return { resourceType: "client-list", clients: clientList };
}

async function getClient(clientId) {
    await Auth0Manager.init();

    const client = await Auth0Manager.getClient(clientId);

    return {
        resourceType: "client",
        id: client.client_id,
        name: client.name,
        description: client.description || "",
        links: [{
            uri: `${apiPrefix}/clients/${client.client_id}`,
            rel: "self"
        },
        {
            uri: `${apiPrefix}/clients/${client.client_id}`,
            rel: "self",
            type: "PUT"
        }]
    };
}

ClientをアップデートするFunctionを追加します。以下、追加後のindex.jsです。

index.js
//index.js
const express = require('express');
const Auth0Manager = require('./Auth0Manager');
const bodyParser = require('body-parser');  //import the body-parser library

require('dotenv').config();

const app = express();
const apiPrefix = '/api';

app.use(bodyParser.json());         // enable the body-parser middleware

//Define the root endpoint
app.get(`${apiPrefix}/clients/:clientId`, async (req, res) => {
    const clientState = await getClient(req.params.clientId);

    res.send(clientState);
});

app.listen(3001, () => {
    console.log('listening on port 3001');
});

//Use the Auth0 Management API wrapper and build the response with hypermedia 
async function getClients() {
    await Auth0Manager.init();

    const clients = await Auth0Manager.getClients();
    const clientList = clients.map(client => ({
        id: client.client_id,
        name: client.name,
        description: client.description || "",
        links: [{
            uri: `${apiPrefix}/clients/${client.client_id}`,
            rel: "client"
        }]
    }));

    return { resourceType: "client-list", clients: clientList };
}

async function getClient(clientId) {
    await Auth0Manager.init();

    const client = await Auth0Manager.getClient(clientId);

    return {
        resourceType: "client",
        id: client.client_id,
        name: client.name,
        description: client.description || "",
        links: [{
            uri: `${apiPrefix}/clients/${client.client_id}`,
            rel: "self"
        },
        {
            uri: `${apiPrefix}/clients/${client.client_id}`,
            rel: "self",
            type: "PUT"
        }]
    };
}

async function updateClient(clientId, updatedData) {
    await Auth0Manager.init();
    await Auth0Manager.updateClient(updatedData, clientId);
}

updateClient()をAPI EndPointから呼び出せるようにコードを修正します。以下、修正後のindex.jsです。

//index.js
const express = require('express');
const Auth0Manager = require('./Auth0Manager');
const bodyParser = require('body-parser');  //import the body-parser library

require('dotenv').config();

const app = express();
const apiPrefix = '/api';

app.use(bodyParser.json());         // enable the body-parser middleware

//Define the root endpoint
app.get(`${apiPrefix}/clients/:clientId`, async (req, res) => {
    const clientState = await getClient(req.params.clientId);

    res.send(clientState);
});

app.put(`${apiPrefix}/clients/:clientId`, async (req, res) => {
    const updatedData = req.body;

    await updateClient(req.params.clientId, updatedData);

    res.sendStatus(200);
});

app.listen(3001, () => {
    console.log('listening on port 3001');
});

//Use the Auth0 Management API wrapper and build the response with hypermedia 
async function getClients() {
    await Auth0Manager.init();

    const clients = await Auth0Manager.getClients();
    const clientList = clients.map(client => ({
        id: client.client_id,
        name: client.name,
        description: client.description || "",
        links: [{
            uri: `${apiPrefix}/clients/${client.client_id}`,
            rel: "client"
        }]
    }));

    return { resourceType: "client-list", clients: clientList };
}

async function getClient(clientId) {
    await Auth0Manager.init();

    const client = await Auth0Manager.getClient(clientId);

    return {
        resourceType: "client",
        id: client.client_id,
        name: client.name,
        description: client.description || "",
        links: [{
            uri: `${apiPrefix}/clients/${client.client_id}`,
            rel: "self"
        },
        {
            uri: `${apiPrefix}/clients/${client.client_id}`,
            rel: "self",
            type: "PUT"
        }]
    };
}

async function updateClient(clientId, updatedData) {
    await Auth0Manager.init();
    await Auth0Manager.updateClient(updatedData, clientId);
}

Root EndPointを起動してリクエストを投げてみます。

$ pwd
~/auth0-restful-management-api
$ node index.js
listening on port 3001

Applicationの"description"をアップデートするリクエストです。xxxxxxxxはターゲットのClient IDを指定します。

curl -H 'Content-Type: application/json' -X PUT -d '
{"description": "This is my app"}'  http://localhost:3001/api/clients/xxxxxxxx

リクエストが成功した確認します。

$ curl localhost:3001/api/clients/xxxxxxxx | jq .
{
  "resourceType": "client",
  "id": "xxxxxxxx",
  "name": "Qiita  Management API Wrapper Mock",
  "description": "This is my app",
  "links": [
    {
      "uri": "/api/clients/xxxxxxxx",
      "rel": "self"
    },
    {
      "uri": "/api/clients/xxxxxxxx",
      "rel": "self",
      "type": "PUT"
    }
  ]
}

RESTful APIをAuth0で保護する

Auth0ダッシュボードの左ペインで"APIs"をクリック、右上の"CREATE API"を押します。

"Name", "Identifier"に任意の名前を入力、"Signing Algorithmはデフォルトのまま"CREATE"を押します。Identifierは実在するURLである必要はありません。管理し易い任意の名前を入力します。

.envに環境変数を追加します。

$ cd ~/auth0-restful-management-api
$ vi .env
CLIENT_DOMAIN="kiriko.auth0.com"
CLIENT_ID="xxxxxxxx"
CLIENT_SECRET="xxxxxxxx"
API_IDENTIFIER="https://restful-dashboard-api"

access_tokenの管理するために必要なパッケージをインストールします。

$ pwd
~/auth0-restful-management-api
$ npm install express-jwt jwks-rsa

index.jsにインストールしたパッケージをインポートする処理を追加します。以下、追加後のindex.jsです。

//index.js
const express = require('express');
const Auth0Manager = require('./Auth0Manager');
const bodyParser = require('body-parser');  //import the body-parser library
const jwt = require('express-jwt');     //<--- newly added
const jwksRsa = require('jwks-rsa');    //<--- newly added

require('dotenv').config();

const app = express();
const apiPrefix = '/api';

app.use(bodyParser.json());         // enable the body-parser middleware

//Define the root endpoint
app.get(`${apiPrefix}/clients/:clientId`, async (req, res) => {
    const clientState = await getClient(req.params.clientId);

    res.send(clientState);
});

app.put(`${apiPrefix}/clients/:clientId`, async (req, res) => {
    const updatedData = req.body;

    await updateClient(req.params.clientId, updatedData);

    res.sendStatus(200);
});

app.listen(3001, () => {
    console.log('listening on port 3001');
});

//Use the Auth0 Management API wrapper and build the response with hypermedia 
async function getClients() {
    await Auth0Manager.init();

    const clients = await Auth0Manager.getClients();
    const clientList = clients.map(client => ({
        id: client.client_id,
        name: client.name,
        description: client.description || "",
        links: [{
            uri: `${apiPrefix}/clients/${client.client_id}`,
            rel: "client"
        }]
    }));

    return { resourceType: "client-list", clients: clientList };
}

async function getClient(clientId) {
    await Auth0Manager.init();

    const client = await Auth0Manager.getClient(clientId);

    return {
        resourceType: "client",
        id: client.client_id,
        name: client.name,
        description: client.description || "",
        links: [{
            uri: `${apiPrefix}/clients/${client.client_id}`,
            rel: "self"
        },
        {
            uri: `${apiPrefix}/clients/${client.client_id}`,
            rel: "self",
            type: "PUT"
        }]
    };
}

async function updateClient(clientId, updatedData) {
    await Auth0Manager.init();
    await Auth0Manager.updateClient(updatedData, clientId);
}

Auth0から発行されるJWT形式のaccess_tokenをチェックする処理を追加します。以下、追加後のindex.jsです。

const express = require('express');
const bodyParser = require('body-parser');
const Auth0Manager = require('./Auth0Manager');
const jwt = require('express-jwt');
const jwksRsa = require('jwks-rsa');

require('dotenv').config();
const apiPrefix = '/api';

const app = express();

app.use(bodyParser.json());

const checkJwt = jwt({
    secret: jwksRsa.expressJwtSecret({
      cache: true,
      rateLimit: true,
      jwksRequestsPerMinute: 5,
      jwksUri: `https://${process.env.CLIENT_DOMAIN}/.well-known/jwks.json`
    }),  
    audience: process.env.API_IDENTIFIER,
    issuer: `https://${process.env.CLIENT_DOMAIN}/`,
    algorithms: ['RS256']
  });

app.use(checkJwt);

app.get(apiPrefix, async (req, res) => {
    const clientsState = await getClients();

    res.send(clientsState);
});

app.get(`${apiPrefix}/clients/:clientId`, async (req, res) => {
    const clientState = await getClient(req.params.clientId);

    res.send(clientState);
});

app.put(`${apiPrefix}/clients/:clientId`, async (req, res) => {
    const updatedData = req.body;

    await updateClient(req.params.clientId, updatedData);

    res.sendStatus(200);
});

app.listen(3001, () => {
    console.log('listening on port 3001');
});

async function getClients() {
    await Auth0Manager.init();

    const clients = await Auth0Manager.getClients();
    const clientList = clients.map(client => ({
        id: client.client_id,
        name: client.name,
        description: client.description || "",
        links: [{
            uri: `${apiPrefix}/clients/${client.client_id}`,
            rel: "client"
        }]
    }));

    return { resourceType: "client-list", clients: clientList };
}

async function getClient(clientId) {
    await Auth0Manager.init();

    const client = await Auth0Manager.getClient(clientId);

    return {
        resourceType: "client",
        id: client.client_id,
        name: client.name,
        description: client.description || "",
        links: [{
            uri: `${apiPrefix}/clients/${client.client_id}`,
            rel: "self"
        },
        {
            uri: `${apiPrefix}/clients/${client.client_id}`,
            rel: "self",
            type: "PUT"
        }]
    };
}

async function updateClient(clientId, updatedData) {
    await Auth0Manager.init();
    await Auth0Manager.updateClient(updatedData, clientId);
}

Root EndPointを起動してリクエストを投げてみます。RESTful APIはAuth0で保護されておりaccess_tokenが必要なためエラーになることを確認します。

$ pwd
~/auth0-restful-management-api
$ node index.js
listening on port 3001
$ curl localhost:3001/api
listening on port 3001
UnauthorizedError: No authorization token was found
    at middleware (/Users/hisashiyamaguchi/auth0-restful-dashboard/auth0-restful-management-api/node_modules/express-jwt/lib/index.js:76:21)
    at Layer.handle [as handle_request] (/Users/hisashiyamaguchi/auth0-restful-dashboard/auth0-restful-management-api/node_modules/express/lib/router/layer.js:95:5)
    at trim_prefix (/Users/hisashiyamaguchi/auth0-restful-dashboard/auth0-restful-management-api/node_modules/express/lib/router/index.js:317:13)
    at /Users/hisashiyamaguchi/auth0-restful-dashboard/auth0-restful-management-api/node_modules/express/lib/router/index.js:284:7
    at Function.process_params (/Users/hisashiyamaguchi/auth0-restful-dashboard/auth0-restful-management-api/node_modules/express/lib/router/index.js:335:12)
    at next (/Users/hisashiyamaguchi/auth0-restful-dashboard/auth0-restful-management-api/node_modules/express/lib/router/index.js:275:10)
    at jsonParser (/Users/hisashiyamaguchi/auth0-restful-dashboard/auth0-restful-management-api/node_modules/body-parser/lib/types/json.js:110:7)
    at Layer.handle [as handle_request] (/Users/hisashiyamaguchi/auth0-restful-dashboard/auth0-restful-management-api/node_modules/express/lib/router/layer.js:95:5)
    at trim_prefix (/Users/hisashiyamaguchi/auth0-restful-dashboard/auth0-restful-management-api/node_modules/express/lib/router/index.js:317:13)
    at /Users/hisashiyamaguchi/auth0-restful-dashboard/auth0-restful-management-api/node_modules/express/lib/router/index.js:284:7
    at Function.process_params (/Users/hisashiyamaguchi/auth0-restful-dashboard/auth0-restful-management-api/node_modules/express/lib/router/index.js:335:12)
    at next (/Users/hisashiyamaguchi/auth0-restful-dashboard/auth0-restful-management-api/node_modules/express/lib/router/index.js:275:10)
    at expressInit (/Users/hisashiyamaguchi/auth0-restful-dashboard/auth0-restful-management-api/node_modules/express/lib/middleware/init.js:40:5)
    at Layer.handle [as handle_request] (/Users/hisashiyamaguchi/auth0-restful-dashboard/auth0-restful-management-api/node_modules/express/lib/router/layer.js:95:5)
    at trim_prefix (/Users/hisashiyamaguchi/auth0-restful-dashboard/auth0-restful-management-api/node_modules/express/lib/router/index.js:317:13)
    at /Users/hisashiyamaguchi/auth0-restful-dashboard/auth0-restful-management-api/node_modules/express/lib/router/index.js:284:7

Auth0ダッシュボードの左ペイン"APIs->作成したAPI"をクリック、"Test"タブをクリックします。

画面中頃の"Response"でaccess_tokenをコピーします。

access_token付きでリクエストを投げます。Clientリストが取得できれば成功です。

$ curl -H "Authorization: Bearer eyJ0xxxxxxxx" localhost:3001/api|jq .
{
  "resourceType": "client-list",
  "clients": [
    {
      "id": "xxxxxxxx",
      "name": "client hogehoge",
      "description": "",
      "links": [
        {
          "uri": "/api/clients/xxxxxxxx",
          "rel": "client"
        }
      ]
    },
    {
      "id": "xxxxxxxx",
      "name": "client foobar",
      "description": "",
      "links": [
        {
          "uri": "/api/clients/xxxxxxxx",
          "rel": "client"
        }
      ]
    },
    {
      "id": "xxxxxxxx",
      "name": "client mokomoko",
      "description": "",
      "links": [
        {
          "uri": "/api/clients/xxxxxxxx",
          "rel": "client"
        }
      ]
    },
----省略----
}

おわりに

この記事では例としてAuth0 Management APIのread:client, update:clientのみを対象のScopeとしていますが、もちろん他のScopeを対象とすることも可能です。ユーザ管理系のScope(ex/read:users, update:users)を対象とすればセルフサービスユーザ管理ポータルを簡単に実装することもできますね。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away