Auth0のFormsを使ってメールアドレス変更機能を実装する

Auth0ではユーザーがセルフサービスでアカウント管理を行うための機能（画面）が提供されていない。たいていのアプリケーションではこの機能は必須となるため、Auth0の外部に自前で実装することになるが、これを最近リリースされた[Forms](https://auth0.com/docs/customize/forms)を使って実現できないか検討した。

なお、Formsの典型的なユースケースとしては同意取得や追加属性収集が挙げられており、アカウント管理機能の実装に利用することは、イレギュラーなケースにあたると考えられる。今回の検証はあくまで技術的な実現性（課題の洗い出し）を目的としている。

### プロトタイプ実装

Auth0 Deploy CLIでエクスポートしたテナント設定を[auth0-forms-change-email](https://github.com/unhurried/auth0-forms-change-email)にプッシュしている。

#### 構成要素

* Forms

  * [Dashboard](https://github.com/unhurried/auth0-forms-change-email/blob/main/forms/Dashboard.json)

    現在のメールアドレスの表示とメールアドレス変更画面へのリンクを配置する画面

    ![Dashboard](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186068/de52812e-0ac0-4ccf-93e4-22af04de22bb.png)

  * [Change Email](https://github.com/unhurried/auth0-forms-change-email/blob/main/forms/Change%20Email.json)

    メールアドレス変更の一連の画面

    ![Change Email](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186068/b3db3df4-1f75-4cca-bfd8-e382adde044a.png)


* Flows

  * [Generate & Send Email OTP](https://github.com/unhurried/auth0-forms-change-email/blob/main/flows/Generate%20%26%20Send%20Email%20OTP.json)

    メールアドレスの受信確認のためのワンタイムパスワードを生成してメール送信を行う。

    ![Generate Email OTP](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186068/fd3a4508-500b-4b1a-ba0b-852153fb3c33.png)

  * [Verify Email OTP & Change Email](https://github.com/unhurried/auth0-forms-change-email/blob/main/flows/Verify%20Email%20OTP%20%26%20Change%20Email.json)

    ワンタイムパスワードの検証を行い、ユーザーのメールアドレスを更新する。

    ![Verify Email OTP](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186068/51338d8a-b361-4134-8871-13b20016640c.png)

### わかったこと

* Formsを利用したメールアドレス変更機能の実装は、下記の要領で実現できる。
  * Authorization Requestにカスタムクエリを付与（プロトタイプ実装では `form`）を付与して、Formsを呼び出す。
  * Flowsのワンタイムパスワード生成・検証 ([Data verification](https://auth0.com/docs/customize/forms/flows/integrations/data-verification)) 機能を利用して、メールアドレスの受信確認を行い、[Update user](https://auth0.com/docs/customize/forms/flows/integrations/auth0#update-user)機能でメールアドレスを更新する。
* ただし、IDトークンやアクセストークンの取得のための機能であるAuthorization Requestを、Formsの呼び出しのためだけに利用して認可コードを返却せずに中断するという、本来の目的通りではない使い方となる。
* ワンタイムパスワードの有効期限は5分固定で、1つのトランザクション10回検証に失敗すると以後は `Rate limit reached` エラーとなる。新規トランザクションのRate Limitは[Rate Limit Configuration](https://auth0.com/docs/troubleshoot/customer-support/operational-policies/rate-limit-policy/rate-limit-configurations)によるようであるが、具体的にどの項目が該当するのかの明記はない。
  * [Limit OTP attempts in Form / Too Many Beats - Auth0 Community](https://community.auth0.com/t/limit-otp-attempts-in-form-too-many-beats/146233)
  * [What is expiry and attemp-failed of "Generate one-time password"'s action in flows? - Auth0 Community](https://community.auth0.com/t/what-is-expiry-and-attemp-failed-of-generate-one-time-password-s-action-in-flows/163605/7)
* ワンタイムパスワードのメール送信は、MailjetもしくはSendGrid以外のサービスを利用する場合、[HTTP Request](https://auth0.com/docs/customize/forms/flows/integrations/http-request)で実装する必要がある。（Amazon SESにも対応してほしいところ。）
* 変更先のメールアドレスが既に他のユーザーに使用されているかどうかを確認する機能は提供されていないため、[Management APIを呼び出すFlow](https://auth0.com/docs/customize/forms/flows/integrations/auth0#custom-api-request)を作って検証するか、Update userの重複エラーをハンドリングして対応する必要がある。
* Formsでは前のStep (画面) に戻る遷移を作ることができないため、Authorization RequestのURLに転送して、最初のStepに戻す方法を取るしかない。
  * 例えば、上記のUpdate userの重複エラーが起きたときには、変更先のメールアドレス入力画面に戻すというケースが相当する。プロトタイプ実装では `Try Again` という文字列をAuthorization RequestのURLのリンクにすることで対応している。

### その他Auth0 FormsのTips

* Step (画面) のタイトルは `Form | All Applications` となる。このカスタマイズはHTMLコンポーネント内でJavaScriptで対応する必要がある。
  * [Forms for Actions: Configure Browser Tab Title per Form - Auth0 Community](https://community.auth0.com/t/forms-for-actions-configure-browser-tab-title-per-form/168087)
* Flowの[Integration](https://auth0.com/docs/customize/forms/flows/integrations)の結果はFormsからは参照できない。Formから参照するには[Shared Variables](https://auth0.com/docs/customize/forms/variables#shared-variables)に設定する必要がある。
* [HTTP Request](https://auth0.com/docs/customize/forms/flows/integrations/http-request)の認証方法はBearer認証もしくはBasic認証のみがサポートされているため、その他の認証方法は別途作りこみが必要となる。
  * 例えば、OAuth2 Client Credentials Grantを利用する場合は、トークン取得用のHTTP Requestを作るか、Actionの処理で予めトークンを取得して[Shared Variables](https://auth0.com/docs/customize/forms/render#inject-custom-data-with-shared-variables-server-side-)で引き渡す必要がある。