Auth0の認可モデル

認証・認可周りのOSS・SaaSの調査として、今回はAuth0の認可モデルについて動作確認をしながら整理した。

#### まとめ

* 基本的な思想は、認証・認可の結果をアクセストークン（JWT形式の署名付きトークン）に埋め込み（ここまでがAuth0の責務）、各Resource Server (API) にてトークンを検証して埋め込まれた情報を元に処理をする、というものとなっている。
* Resource Serverで利用可能なトークンを発行するには、Authorization Requestの`audience`パラメータ（Auth0独自仕様）にResource Serverの識別子を指定する。これによりResource ServerのScopeの検証処理が実行される。
* ビルトインの認可ルールとしてRBACが利用できる。Resource Serverの`enforce_policies`を`true`に設定することで有効化できる。※ その他にも独自の認可ルールをActionとして登録することができる。（参考：[Auth0 Actions](https://auth0.com/docs/customize/actions)）
  * UserもしくはUserに設定したRoleに対して、利用可能なScopeを定義する。Authorization Requestの`scope`パラメータにて要求されたScopeと利用可能なScopeの積部分がアクセストークンの`scope`クレームに設定される。
  * 追加でResource Serverの`token_dialect`を`access_token_authz`に設定した場合、要求されたScopeに関わらず利用可能なScopeがアクセストークンの`permissions`クレームに設定される。
* 1つのトークンは1つのResource Serverのみしか対象に設定できない制約がある。（`audience`パラメータには1つの識別子のみ設定可能。）複数のResource Serverでトークンを共有したい場合は1つとして登録する必要がある。（参考：[Configure Logical API for Multiple APIs](https://auth0.com/docs/get-started/apis/set-logical-api)）

![データモデル](https://gist.githubusercontent.com/unhurried/9ae0db89ac8838a5333072fa88aafc0a/raw/8659859f5ddbbd6d5ad9425a0afc9e45c2debf52/auth0-data-model.svg)

#### 動作確認内容

（作業はWebコンソールではなく、[Management API](https://auth0.com/docs/api/management/v2)をcurlで呼び出して行う。）

Management API用のトークンとテナントのURIを設定する。

```shell
TOKEN=eyJ...
BASE_URI=https://xxx.xx.auth0.com
```

Resource Server (API) を作成する。

```shell
RESOURCE_SERVER_ID=$(curl -s -X POST ${BASE_URI}/api/v2/resource-servers \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d @- << EOF | jq -r .id
{
  "identifier": "resource-server-1",
  "scopes": [
    { "value": "scope-1" }, { "value": "scope-2" }, { "value": "scope-3" }
  ]
}
EOF
)
```

Client (Application) を作成する。curlから試せるようにPassword Grantを有効化する。

```shell
RESULT=$(curl -s -X POST ${BASE_URI}/api/v2/clients \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d @- << EOF | jq .
  {
    "name": "client 1",
    "grant_types": [ "password" ]
  }
EOF
)
CLIENT_ID=$(echo ${RESULT} | jq -r .client_id)
CLIENT_SECRET=$(echo ${RESULT} | jq -r .client_secret)
```

Role（test role 1）を作成する。

```shell
ROLE_ID=$(curl -s -X POST ${BASE_URI}/api/v2/roles \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d @- << EOF | jq -r .id
  { "name": "test role 1" }
EOF
)
```

Roleに利用可能なScope (Permission) を割り当てる。

```shell
curl -s -X POST ${BASE_URI}/api/v2/roles/${ROLE_ID}/permissions \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d @- << EOF | jq .
  {
    "permissions": [
      {
        "resource_server_identifier": "resource-server-1",
        "permission_name": "scope-1"
      },
      {
        "resource_server_identifier": "resource-server-1",
        "permission_name": "scope-2"
      }
    ]
  }
EOF
```

Userを作成する。

```shell
EMAIL=user-1@example.com
PASSWORD=$(echo $RANDOM | sha256sum | base64)
USER_ID=$(curl -s -X POST ${BASE_URI}/api/v2/users \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d @- << EOF | jq -r .user_id
  {
    "email": "${EMAIL}",
    "password": "${PASSWORD}",
    "connection": "Username-Password-Authentication"
  }
EOF
)
```

UserにRoleを割り当てる。

```shell
curl -s -X POST ${BASE_URI}/api/v2/users/${USER_ID}/roles \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d @- << EOF | jq .
  {
    "roles": [ "${ROLE_ID}" ]
  }
EOF
```

この状態でアクセストークンを発行してみる。

```shell
RESPONSE=$(curl -s -X POST \
  "${BASE_URI}/oauth/token" \
  -d "grant_type=password" \
  -d "client_id=${CLIENT_ID}" \
  -d "client_secret=${CLIENT_SECRET}" \
  -d "username=${EMAIL}" \
  -d "password=${PASSWORD}" \
  -d "scope=openid scope-1 abc" \
  -d "audience=resource-server-1"
)
echo $RESPONSE | jq .
jq -R 'fromjson | .access_token | split(".") | .[1] | @base64d | fromjson' <<< $RESPONSE
```

→ `aud`クレームにResource Serverの識別子、`scope`クレームにAuthorization Requestで要求したものが設定されている。

```json
{
  "aud": [
    "resource-server-1",
    "https://xxx.xx.auth0.com/userinfo"
  ],
  "scope": "openid profile email address phone scope-1",
  ...
}
```

Resource Serverの下記の設定を変更して、再度アクセストークンを発行する。

* `enforce_policies`を`true` に設定する。（Enable RBACを有効化する。）
* `token_dialect`を`access_token_authz`に設定する。（Add Permissions in the Access Tokenを有効化する。）

```shell
curl -s -X PATCH ${BASE_URI}/api/v2/resource-servers/${RESOURCE_SERVER_ID} \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d @- << EOF | jq .
{
  "enforce_policies": true,
  "token_dialect": "access_token_authz"
}
EOF
```

→ `permissions`クレームにUserが利用可能な全てのScopeが追加で設定されるようになる。

```json
{
  "aud": [
    "resource-server-1",
    "https://xxx.xx.auth0.com/userinfo"
  ],
  "scope": "openid profile email address phone scope-1",
  "permissions": [
    "scope-1",
    "scope-2"
  ]
}
```