フューチャー技術ブログ

Auth0のRulesを使って認証認可を自在にカスタマイズする

Auth0をカスタマイズして使うに当たって必要になるRulesについて、概要と開発に関するTipsを説明します。
Auth0のRulesの使い方については、Auth0公式のRules Best Practicesに様々な役立ち情報が載っているのですが、分量が多くてしんどい(全て英語な上に印刷するとA4で18ページ分…)です。
この記事ではRules Best Practicesの中でも特に役に立つと思われる情報と、その他Rules開発で得られた知見について書いていきます。

Auth0に関する概要については弊社 山田の記事を、Auth0の二要素認証に興味のある方は弊社 先山の記事を御覧ください。

Rulesとは

Auth0のRulesとは、

  • アクセストークンやIDトークンの取得後に実行される
  • 任意のJavaScriptで書かれたコードを実行して
  • 認証認可の結果を編集したり、追加の処理を行う事のできる機能

となっています。
https://auth0.com/docs/best-practices/rules

Auth0にはOIDCを使った認証認可フローに必要な機能が備わっているのですが、Rulesの機能を使うことによりプロダクト固有の様々な要求に答えることが出来ます。

Rulesの作り方

Auth0には代表的なRulesの活用方法のサンプルを多数用意していて、それらをそのまま使うことでもある程度の認証認可のカスタマイズが可能となっています。
ここではサンプルを用いたRulesの作成手順を説明します。

まず左のメニューからRulesを選択し、右上のCREATE RULEをクリックします。

空のルールか、用意されたテンプレートを選択します。
今回は、「IP Address whitelist」というテンプレートを使用してルールを作成します。

ソースコードが表示されるので、許可したいIPのリストを編集してSAVE CHANGESを押すとルールの追加は完了です。

ルールを追加が反映されると、特定のIPアドレスから以外のアクセスはログイン出来ないような制御が追加されます。
このような形でRulesを追加していくことにより、細かなアクセス制御やトークンに与えられる情報の変更等様々なカスタマイズを施すことが出来ます。

Rulesの書き方

テンプレート以外のRulesが必要になる事も多々あると思うので、基本的なRulesの書き方について説明します。
以下が空のルールのコードです。

function (user, context, callback) {
// TODO: implement your rule
return callback(null, user, context);
}

ルールは、

  • ユーザ情報、context, コールバック関数を受け取り、
  • 受け取ったコールバックを呼び出す

必要があります。
ルールが受け取る情報は、

  • user: ログインしたユーザに関する情報が入ったオブジェクト
  • context: 現在のログイン処理に関する情報が入ったオブジェクト

の2つになります。内容の正体はAuth0の公式ドキュメントを確認してください。

callbackへ渡す引数は、1つ目はエラーとなっています。nullは正常に処理が完了したことを表し、この後続のRulesが引き続き実行されます。
2つ目、3つ目は次のRuleに渡すuserやcontextを表しています。これらを編集すると後続の処理に特定の値を渡したり、実際にログインされるユーザをRule側で変更したりすることが出来ます。

認証にエラーが発生した場合、以下のような形でエラーを返す事が推奨されています。

return callback(new UnauthorizedError("IP is out of range"))
return callback(new Error("Unexpected error"))

UnauthorizedErrorの引数が拒否された理由を表します。
callbackでnull以外何を返しても認証エラーとみなされますが、Unauthorizedオブジェクトは原因が分かっている認証エラー、Errorオブジェクトは内部エラーを表現するのに使用するのが正しいようです。Auth0公式ドキュメントより

独自Rulesの開発Tips

以上がRulesの概要なのですが、実際に開発する上で知っておくと良いTipsや、最初の開発の際にハマりやすいポイントが多数あるのでいくつかをピックアップしてここに記します。

ログの見方について

Rules内でconsole.logを使用しても、Auth0のログインページやログイン時のレスポンスで出力結果を見ることは出来ません。
しかし、拡張機能のReal-time Webtask Logsを使うことによってログを見ることが出来るようになります。

拡張機能をインストールして使用すると、ブラウザ経由から以下のような形でログインの試行のログやconsole.logで出力した内容を確認することが出来ます。

Real-time Webtask Logsはデバッグには非常に強力なツールなのですが、本番で運用する際には個人情報が含まれるuserオブジェクトやcontextオブジェクトはコンソールには出さないよう注意が必要です。

環境変数の使い方について

Rulesを開発していると、ソースコード上ではなく環境変数で管理したい値がだいたい出てきます。
例としては、

  • 環境によって異なる外部APIの接続先情報
  • APIキー等公開したくない情報

が挙げられます。
Auth0では全てのRulesで共通の環境変数を定義することが出来ます。
環境変数を定義するためにはRulesを開き、以下の赤い四角の部分を使用して登録します。

環境変数を使用する際には、以下のようにconfigurationオブジェクトを使用してアクセスします。

function (user, context, callback) {

var SOME_VALUE = configuration.SOME_KEY;
// do something with value

return callback(null, user, context);
}

使用できるライブラリについて

RulesではAWSのLambda等と異なりライブラリのコードも含めてアップロードすることは出来ないので、インストールされているライブラリに実装が依存します。
そうなると、使用できるライブラリ一覧が欲しくなってきます。
以下のページで使用できるモジュールの一覧を確認することが出来ます。
https://auth0-extensions.github.io/canirequire/
基本的なモジュールは全て含まれているので、困ることは少ないと思います。
もしどうしても必要なライブラリが含まれていない場合はリクエストすることも出来るみたいです。

Rulesの中でAuth0自身のAPIを使用する方法について

Auth0のRules内では、一部のAuth0APIが簡単に使用できるような機能が提供されています。
以下Auth0のルール内でユーザのメタデータを更新するためのコードです。

auth0.users.updateAppMetadata(user.user_id, user.app_metadata);

auth0というオブジェクトにアクセスすることで、現在のauth0環境関連の情報(ドメイン等)や一部のAPI呼び出しを行うことが出来ます。
しかし、このauth0オブジェクトに付与されている権限は一部の権限しかないため(read:usersとupdate:usersのみ)その他の操作を行おうとすると拒否されてしまいエラーとなります。
全てのauth0の操作をRules上で行うためには、Rules上でAuth0からアクセストークンを取得するコードを書く必要があります。

例として以下に、ログインしてきたユーザを全て削除してしまうRuleを示します(危険なルールなので、もちろんですが本番環境等では絶対に動かさないでください!)

function (user, context, callback) {

var ManagementClient = require('auth0@2.9.1').ManagementClient;
// Auth0のユーザ削除権限を持つクライアントを生成する
var managementAuth0 = new ManagementClient({
domain: auth0.domain,
clientId: configuration.application_client_id,
clientSecret: configuration.application_client_secret,
scope: 'delete:users'
});

// ユーザ削除を行う
managementAuth0.users.delete({ id: user.user_id }, function (err) {
if (err) {
console.error(`failed to delete user ${user.user_id}`);
console.error(err);
}
callback(err, user, context);
});
}

このソースを実行する前準備として、Rulesでの実行用にMachine to Machine用のアプリケーションを1つ登録しておき環境変数にclient_idとclient_secretを登録します。
これを行うことにより、任意のAuth0APIを呼び出すことが出来るようになります。
もちろん必要ない場合は、デフォルトで与えられているauth0のクライアントを使用するべきなのでご注意ください。

サインアップ時のみRulesを実行する方法について

Rulesが実行されるタイミングはトークンを発行した後になります。
ログイン時とサインアップ時は両方トークンが発行されるのですが、Rulesに渡される情報のみからはそのリクエストがログインかサインアップかわかりません。
そのため、サインアップ時のみ実行したい処理がある場合は少し工夫する必要があります。
調べたところ、ユーザ固有のメタデータを保存する領域であるapp_metadataを使用して実現する方法がメジャーのようです。(例1, 例2)

以下がサインアップ時のみ特定の処理を実行するソースコードです。

function (user, context, callback) {

// 既にサインアップ処理が完了している場合は何もしない
if (user.app_metadata.signedup) return callback(null, user, context);

// do something related to signup

// ユーザのAppMetadataを更新し、サインアップフラグをtrueにする
user.app_metadata.signedup = true;
auth0.users.updateAppMetadata(user.user_id, user.app_metadata);

return callback(null, user, context);
}

サインアップ時の更新処理が正常に完了した後に、ログインユーザのメタデータでサインアップ処理が完了したことを示すフラグをONにすることで2回目以降は実行されないように制御しています。

終わりに

Rulesは任意のコードが実行可能な非常に強力な機能ですが、処理を追加しすぎるとログイン処理が遅くなってしまうリスクがあります。

そのため、認証認可のフローで必ずやらなければならない最小限の内容を行うに留めるのが良いでしょう。

ここでは語れませんでしたが、より多くの実行タイミングが選べるHooksという機能がBetaで公開されています。将来的にはHooksを活用したほうがより柔軟で綺麗な処理を書くことが出来るようになりそうです。