権限制御可能な永続化APIキーをAuth0で発行する

# はじめに

[株式会社hokan](https://hkn.jp)でプロダクトマネージャーやエンジニアをしている宮といいます。

先日、Auth0への移行に関する記事を投稿したのですが、弊社ではAuth0を利用し始めて半年ほどです。
ID管理をしていく中で「永続化したAPIキーをユーザー向けに発行する」機構が必要になったのですが、Auth0は基本的に有効期限を持ったトークンしか発行できません。
この機構について、かなり苦慮したものの割とキレイに実装できたのでその方法について共有しようと思います。


# この記事の前提

### Auth0の概要について
Auth0は、サービスごとに分散するアカウント情報の管理を一元化したり、認証方法の強化に関する統合認証基盤の一種です。
Auth0の概要については[こちらの記事](https://qiita.com/furuth/items/68c3caa3127cbf4f6b77)が詳しく解説してくれているので、Auth0をご存知ない方はそちらをまずはご覧ください。

### Auth0を採用した理由など
マイクロサービス化や認証機能の実装の効率化などを狙っています。
[前回の記事](https://qiita.com/miyatomo/items/3f6ef4fe315143cc584d#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB) にて少しだけ記載しているのでご覧ください。

### 当記事にて取り扱うユースケースと背景
当記事では、下記のような要件を持ったAPIキーをAuth0で完結させる、というユースケースを取り扱います。
それぞれ、Auth0の既存の機能で実現するには若干機能が足りなかったことから可能な限りAuth0に寄せつつ、自前で構築しています。

1. ユーザーが無期限なAPIキーを生成することができる
1. 生成したAPIキーごとにユーザーが権限を付与し、別々の権限を持たせることができる
1. ユーザーが所属する組織ごとにAPIキーを特定できる
1. APIキーの要件として必要な機能として、下記を実装できる  
  ・ ユーザーが明示的にAPIキーをローテーションでき、古いAPIキーは即時的に使用できなくする  
  ・ ユーザーが明示的にAPIキーを削除できる  
  ・ ユーザーが明示的にAPIキーの権限を変更できる  

例えば、 [Auth0コミュニティのフォーラム](https://community.auth0.com/t/how-to-implement-api-keys-using-auth0/6296/5) では、APIキーを実装予定であることには触れられているものの、APIキーはM2M認証でやってくれというのが見解です。

Auth0のM2M認証を使用する場合、Client IDとClient Secretをユーザーに開示しないといけなかったりその管理はユーザーに任せたり、と若干不安感が残ります。
また、トークンがどのユーザーに紐づくのか、がかなり分かりづらくなる上にAuth0のUI上、Applicationsに大量のM2M認証が並ぶのは管理上もあまり気が進みませんでした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239882/205a7562-e0d4-023a-9bb7-a7bcf5957d4f.png)

というわけで、M2M認証でアクセストークン発行するの嫌だなぁ、と考えていた中でメンバーと一緒に考えてできた仕掛けが結構良い仕組みな気がするので共有します。

## 概要

今回の実装では、APIキーはAuth0の標準のDatabase Connectionsを利用します。
これはユーザーID or メールアドレスとパスワードにより認証を行うもので、一番標準的な機能です。

今回の実装は、個々のAPIキーをこのユーザーに見立て、① 送信されてきたAPIキーをデコード＆検証して問題がなく、② APIキーからユーザーID/パスワードを取り出し、③ Auth0でユーザー認証を行うことでAPIキーが持つ権限を [RBAC](https://dev.classmethod.jp/articles/use-role-based-access-control-in-auth0/) で取得します。


### APIキーの発行機構
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239882/034e616c-842f-2ee5-b8ce-89f482e1cfe3.png)

APIキーはAuth0のユーザーに見立てています。
したがって、単純にAPIキー用のDatabase Connectionに、Management APIでユーザーを作成するというフローになるのですが、この時APIを発行するまでに下記のような手順を踏んでいます。

**APIキー発行の手順**

1. APIキー発行のリクエストを受ける。
1. 完全にランダムなユーザーIDとパスワードを生成する
1. そのランダムなユーザーID、パスワード（+一意でテキトーなメールアドレス）で Management APIのCreate Userをコール  
※ この時、APIキーに権限を付与する場合は、Auth0のPermissionを使用する。  
※ user_metadataに独自情報を書き込む、みたいなのもアリ。所属組織をいれるなどすればマルチテナント環境でも適用できます
1. Auth0からユーザー情報が返却
1. 返却されたユーザーのauth0のUserIDとPermissionの一覧を自前のデータベースに保存しておく  
    ※ APIキーのローテーションやAPIキーの削除機能で利用します
1. 2 で生成したユーザーID、パスワードを何らかの方法でエンコードしたりしてAPIキーを生成  
    ※ APIキーが改ざんされても良い様にハッシュなどで検証できるようにしましょう

この時、ポイントはM2M認証は一切利用せず、ユーザー認証のみで対応していることです。
キー生成時のエンコードロジックは自前で必要ですが、ハッシュでの検証をちゃんとする、であるとかソルト用意する、みたいなくらいやれば大丈夫かなとも思っています。

### APIキーの認証機構
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239882/001e5671-634f-a099-30d6-04cbdedaa11b.png)

APIキーの解析と認証は上図のように行うことができます。
API内での認可などは4. の処理まで進んでしまえばAuth0のJWTベースで行えるようになります。
既存の機構としてAuth0のJWTを使うようにしている場合、ほぼ同等のAPIの機能を使って認可処理などが行えるはずです。
JWT返却時にRBACを利用していればアクセストークンに権限が付与されますし、Auth0のルールによってuser_metadataをはやしたりすることもできます。

ポイントは、APIキーを解析した結果として取得できるユーザーIDとパスワードは永続化されていることです。
一方で、Auth0の認証によって使用できるJWTは、その時のユーザーの権限を反映できます。


### APIキーの削除
APIキーに対する各処理については、若干自分のサーバのリソースを使用します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239882/b0a36838-a066-f6c1-80a2-940954cc33d2.png)

APIキーを削除する場合、僕が作っている機構であれば自社DBからAuth0のUserIDを取得するようにしています。
そのAuth0のUserIDを使用してManagement APIをコールすればAPIキーの削除と同義になります。

ただ、厳格にやる場合、Auth0のUserIDすら自社に残したくない、というのもあるかもしれません。
その場合、削除指示を行う場合に対象のトークンをヘッダにつけてもらうなどすれば、自社のDBで情報を持つ必要はありません。

### APIキーのローテーション
APIキーに対する各処理については、若干自分のサーバのリソースを使用します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239882/0b062de0-4486-7711-76b0-935567940084.png)

APIキーの再作成（ローテーション）処理については、文字通り再作成するのみです。
前述の２つの機能の組み合わせ（APIキーの発行とAPIキーの削除）で実現できます。

この時、僕が作っているものでは自社DBに、各トークンのAuth0のUserIDとPermissionsのリストを保存しています。
これを使って簡易的にユーザーを削除＆同じ権限を持った新しいユーザーを作成し、APIキーにエンコードする処理を行っています。

### APIキーの権限更新

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/239882/a0e2a914-430b-1226-e4a7-3932778502a4.png)

APIキーの権限更新の場合、上図のようにDBからAuth0のUserIDを取得し、そのIDにてPermissionを更新しに行っています。
この処理はManagement APIで単純に更新するのみですが、２回APIを打たないといけないので、自社DBで権限を管理するのもありだと思います。

#### 【補足】 サーバサイドでの認証処理について
このAPIキーの認証処理は原則としてサーバサイドでDatabase Connectionでのユーザー認証が行われています。
これは、下記のような方法によってAuth0のAuthentication APIによって実現していますが、若干レガシーなやり方とのことなので注意が必要です。


**APIベースでのユーザー認証の方法**

1. クライアントのアップデート  
下記のマネジメントAPIをコールし、 `http://auth0.com/oauth/grant-type/password-realm` のgrant_typeを許可。
※リクエストのボディはjsonを入れればOKです。

  https://auth0.com/docs/api/management/v2#!/Clients/patch_clients_by_id

``` json
{
  "grant_types": [
    "authorization_code",
    "implicit",
    "refresh_token",
    "password",
    "http://auth0.com/oauth/grant-type/password-realm"
  ]
}
```
２． Authenticate APIにより認証
  下記のAPIにより、特定のコネクションに対してユーザー/パスワードで認証が可能

https://auth0.com/docs/api/authentication#resource-owner-password

## さいごに
今回はAPIキーの発行と認証、および各処理についてツラツラと記載しました。
一番最初に載せたフォーラムのやりとりでは、「永続化APIキーは鋭意実装中」とされているものの、4年ほどまだ実装されていません。
Organizationsの機能など拡充の早いAuth0ですが、むしろ既存機能でここまでやれるのが凄いなと思います。
永続化APIキーを利用するならこういった独自実装も可能で、柔軟性の高さにビックリしました。

hokanでは今年頭に認証基盤をAuth0に移行して、外部API用のトークン発行の機能、多要素認証の機構や社内システムへのSSO機能など、すごいスピードで色々な機能を実装できています。
また、さらに最近はADやLDAPとの接続の話なんかも話が出始めています。

一昔前では考えられなかったようなスピード感で認証基盤の拡張ができる程Auth0の拡張性は相当高く、単純にものごとのスピードを上げてくれます。
ユーザー移行でネックになることなく、こういった恩恵を日本のサービスでももっと享受できれば良いなと思ってます！