Auth0を使って「サードパーティにAPIを公開する」システムを作る #Auth0JP

2019.11.18

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

「サードパーティにAPIを公開する」システムを作りたい場合

本記事では、認証・認可プラットフォームである「Auth0」を利用して 自社サービスをサードパーティにAPIを公開する というユースケースの実現方法を考えてみたいと思います。

  • Auth0を認証基盤とした自社サービスを持っている
  • サードパーティの開発者向けに、自社サービスを使ったWebアプリを作ってもらいたい(Mashupのイメージ)

上記の要件だけであれば比較的シンプルに作れますが、以下の要件を加えて考えたいと思います。

  • 自社サービスにはオーガナイゼーション機能があり、1ユーザーが複数のオーガナイゼーションに加入できる
  • サードパーティWebアプリをユーザーが使用するには、どのオーガナイゼーションで使うか選ぶ(選んだオーガナイゼーションのみで利用できる)

ポイントは ログインを行わないとオーガナイゼーションを選択できない という点です。ログイン前に、ログインしようとしているユーザーがどのオーガナイゼーションに所属しているかは分かりません。そのため 2段階の認可 を行うようにする必要があります。

アーキテクチャ

メタデータ

Auth0のユーザーにはメタデータを持たせることができます。ユーザーが読み書きできる user_metadata と、ユーザーからは読み取りのみできる app_metadata のいずれかにキーバリュー形式で自由にユーザー情報を加えることができます。

今回は app_metadata の中にオーガナイゼーションの情報を入れるようにします。

RBAC

Auth0のRBAC (Role-Based Access Control)を使うと、ユーザーから許可されたパーミッションを含めたアクセストークンを取得することができます。

独自のAPIに対してのパーミッションを自由に定義し、そのパーミッションをユーザーに割り当てます。その上でWebアプリを認証フローの中でユーザーに割り当てたパーミッションへのアクセスを許可してもらうことで、アクセストークンに含めることができます。

今回は各オーガナイゼーションごとに、アクセス権限となるパーミッションを1つ定義し、所属しているユーザーに割り当てるようにします。

構築

それでは構築する手順をご紹介したいと思います!

今回は Organization A, Organization B という2つのオーガナイゼーションを取り扱うこととします。

テナントの作成

まずテナントを作成し、サンプルアプリケーションが動作するところまで行なっておきます。

手順は本記事では割愛しますが、以下の導入記事を公開しておりますので参考にしてください。

これから始めるAuth0 – ノンコーディングでサンプルアプリケーションを動かそう! #Auth0JP

今回はVue.jsのサンプルアプリケーションを使います。

RBACの設定 - APIとPermissionの定義

RBACの設定を行います。Auth0 Dashboardから「API」を選び、「CREATE API」からAPIを作成します。

「Permissions」タブで、各オーガナイゼーションへのアクセス許可となるPermissionを1つずつ作ります。

今回は手作業で追加しましたが、例えばオーガナイゼーションが作成される処理の中でAuth0 Management APIを使って自動生成する形で組んだりすると良いでしょう。

ユーザーへのメタデータ設定とPermission付与

今回は手作業で、テスト用のユーザーをオーガナイゼーションに所属している状態を作っていきます。

今回のアーキテクチャでは、ユーザーのメタデータを使ってオーガナイゼーションに所属していることを表現します。

まず「Users」から「CREATE USER」でテストユーザーを作成します。

「Details」タブの「app_metadata」を以下のようにし、保存します。

{
  "orgs": ["a", "b"]
}

次に「Permissions」タブを開き「ASSIGN PERMISSIONS」を押し、先ほど定義したPermissionを付与します。

次に、Ruleを使ってユーザーのメタデータがID Tokenに含まれるようにします。「Rule」を開き、「CREATE RULE」をクリックし「empty rule」を選びます。

以下のコードを記述します。

function (user, context, callback) {
  if (user.app_metadata && user.app_metadata.orgs) {
    context.idToken['https://example.com/orgs'] = user.app_metadata.orgs;
  }
  callback(null, user, context);
}

これでログイン後、ID Tokenに https://example.com/orgs というネームスペースに値が入ってくるようになります。

Webアプリの実装

次に、Webアプリのソースコードを修正します。ナビゲーションバーのメニューに、オーガナイゼーションの切り替えメニューを用意します。管理画面でよくある機能イメージです(例 : AWS Management ConsoleのSwitch Role)。

src/components/NavBar.vue を開き、ナビゲーションにアイテムを追加します。

<li class="nav-item dropdown" v-if="$auth.isAuthenticated">
  <a
    class="nav-link dropdown-toggle"
    href="#"
    id="profileDropDown"
    data-toggle="dropdown"
  >
    <img
      :src="$auth.user.picture"
      alt="User's profile picture"
      class="nav-user-profile rounded-circle"
      width="50"
    />
  </a>
  <div class="dropdown-menu dropdown-menu-right">
    <div class="dropdown-header">{{ $auth.user.name }}</div>
    <router-link to="/profile" class="dropdown-item dropdown-profile">
      <font-awesome-icon class="mr-3" icon="user" />Profile
    </router-link>
    <a id="getAccessTokenBtn" href="#" class="dropdown-item" @click.prevent="getAccessToken(item)" v-for="item in $auth.user['https://example.com/orgs']" v-bind:key="item">
      <font-awesome-icon class="mr-3" icon="building" />Switch to {{ item }}
    </a>
    <a id="qsLogoutBtn" href="#" class="dropdown-item" @click.prevent="logout">
      <font-awesome-icon class="mr-3" icon="power-off" />Log out
    </a>
  </div>
</li>

なお、上記に font-awesome-icon を使っていますが、同じように使用する場合は src/main.js を以下のように修正してください。

import { faBuilding, faLink, faUser, faPowerOff } from "@fortawesome/free-solid-svg-icons";

...

library.add(faBuilding, faLink, faUser, faPowerOff);

次に、同じく NavBar.vue コンポーネントに getAccessToken() メソッドを作ります。

methods: {
  async getAccessToken(org) {
    const accessToken = await this.$auth.getTokenWithPopup({
      audience: 'https://example.com/orgs',
      scope: `write:org_${org}`
    });
    console.log(accessToken);
  }
}

アクセストークンの取得には getTokenWithPopup() メソッドを使います。こうすることで scope を要求する際に、認可画面をポップアップウインドウで表示することができます。

動作確認

それでは、一連の動作確認を行ってみましょう。まずは先ほど作成したユーザーでログインします。

次にナビゲーションバーのメニューを開き、まずは「Switch to a」をクリックします。

認可が表示され、アクセストークンが取得できました。

アクセストークンを jwt.io を使ってデコードすると scopepermission が取得できていることがわかります。scope では write:org_a を要求していることが分かります。

{
  "iss": "https://dev-cw0xum-z.auth0.com/",
  "sub": "auth0|5dd21c1b07ef470efc08d0bf",
  "aud": [
    "https://example.com/orgs",
    "https://dev-cw0xum-z.auth0.com/userinfo"
  ],
  "iat": 1574058601,
  "exp": 1574145001,
  "azp": "7TuIkOkquB7QexeUMQSgVe0Dl0EPfPI0",
  "scope": "openid profile email write:org_a",
  "permissions": [
    "write:org_a",
    "write:org_b"
  ]
}

次にナビゲーションバーのメニューを開き、まずは「Switch to b」をクリックします。

今度は scope が変わり、write:org_b を要求していることが分かります。

{
  "iss": "https://dev-cw0xum-z.auth0.com/",
  "sub": "auth0|5dd21c1b07ef470efc08d0bf",
  "aud": [
    "https://example.com/orgs",
    "https://dev-cw0xum-z.auth0.com/userinfo"
  ],
  "iat": 1574058601,
  "exp": 1574145001,
  "azp": "7TuIkOkquB7QexeUMQSgVe0Dl0EPfPI0",
  "scope": "openid profile email write:org_b",
  "permissions": [
    "write:org_a",
    "write:org_b"
  ]
}

ちなみに、以下のリクエストを行った場合は scope は付与されません。

  • そもそも存在しない Permission を指定した場合
  • User が持っていない Permission を指定した場合

まとめ

Auth0のRBAC機能を使って、選択した所属(オーガナイゼーション)に対する権限付与を実装してみました。メタデータやPermissionなどは定義が自由に行えるので、様々なユースケースにフィットさせることができます。ぜひ活用してください。

参考