[Elixir] Phoenix1.7で入るphx.gen.authのLiveView化を掘り下げてみる

この記事は [Elixir Advent Calendar 2022](https://qiita.com/advent-calendar/2022/elixir) カレンダー1の22日目です。昨日は @torifukukaiou さんでした！

https://qiita.com/torifukukaiou/items/d411ab7c0dc38908666f

---

Phoenix1.6で `mix phx.gen.auth` が使えるようになり、コマンド一発で認証システムを実装できるようになりました :tada: が、LiveView実装ではないので、アカウント周りだけ別でUI実装しないといけなかったり、他がLiveView実装だとController（非LiveView）と行き来しないといけなかったりと、少し不便でした。

そんな中、Phoenix1.7でphx.gen.authがLiveView化するとの情報が！ということで、まだRCではありますが、phx.gen.authで生成されるLiveView周りのコードを見ていきます :eye::eye: 

# 公式ドキュメント（HexDocs）を見比べてみる

Phoenixの公式ドキュメントでは、「GUIDES」と「MIX TASKS」のそれぞれに `phx.gen.auth` についての解説ページがあります。

| | GUIDES | MIX TASKS |
| --- | --- | --- |
| 1.6.15 | https://hexdocs.pm/phoenix/1.6.15/mix_phx_gen_auth.html | https://hexdocs.pm/phoenix/1.6.15/Mix.Tasks.Phx.Gen.Auth.html |
| 1.7.0-rc.0 | https://hexdocs.pm/phoenix/1.7.0-rc.0/mix_phx_gen_auth.html | https://hexdocs.pm/phoenix/1.7.0-rc.0/Mix.Tasks.Phx.Gen.Auth.html |

Phoenix1.7での変更点を含め細かい違いはありますが、LiveView化については特に言及されていません。単に更新が追いついていないだけでしょうか :thinking: 

# Phoenix1.7.0-rc.0のプロジェクトを作成する

本題ではないのでさらっと。途中のYes/NoはすべてYesを選択しましょう。

```bash
docker run --rm -it -p 4000:4000 elixir:1.14.2-alpine sh
# 以下、Dockerコンテナ内でのコマンド実行
mix archive.install hex phx_new 1.7.0-rc.0
mix phx.new sample --database sqlite3
cd sample
apk add build-base inotify-tools
sed -i -e 's|{127, 0, 0, 1}|{0, 0, 0, 0}|' config/dev.exs
mix ecto.create
mix phx.server
```

これで http://localhost:4000 にアクセスできるようになります。

# 実際に `mix phx.gen.auth` を実行してみる

Context名が `Accounts` 、Schema名が `User` 、テーブル名が `users` となるように実行します。

```bash
mix phx.gen.auth Accounts User users
```

```:出力その1
An authentication system can be created in two different ways:
- Using Phoenix.LiveView (default)
- Using Phoenix.Controller only
Do you want to create a LiveView based authentication system? [Yn]
```

LiveViewを使うかどうか聞かれましたね！今回はYesを選択しましょう。

```:出力その2
(ファイルを作成するログがたくさん出るが、省略)

Please re-fetch your dependencies with the following command:

    $ mix deps.get

Remember to update your repository by running migrations:

    $ mix ecto.migrate

Once you are ready, visit "/users/register"
to create your account and then access "/dev/mailbox" to
see the account confirmation email.
```

とりあえず、言うとおりにコマンドを実行し、Phoenixを起動しましょう。

```bash
mix deps.get
mix ecto.migrate
mix phx.server
```

| アカウント登録 | ログイン | メールアドレス/パスワード変更 | パスワードリセット |
| --- | --- | --- | --- |
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28249/11897e19-5d9c-0ad3-894a-deb64782f669.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28249/5a0cdd4d-9191-4f5e-e341-d27233cce454.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28249/b4710963-a1d5-f6fc-596d-28f77d6d13cd.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/28249/41e00d1f-9499-bf85-04c8-2e020d177d5c.png) |

これだけの機能がコマンド一発でできあがるのはステキですね :sparkles: 

ここまでのソースコードをGitHubにアップしています。ビジネスロジックはPhoenix1.6までと変わらないので今回はLiveView周りだけを見ていきますが、興味がある方は他のコードも見てみてください :muscle: 

https://github.com/mokichi/phx-gen-auth-1-7-sample

## ルーティング

[lib/sample_web/router.ex](https://github.com/mokichi/phx-gen-auth-1-7-sample/blob/main/lib/sample_web/router.ex)

アカウント周りの記述がいろいろと追加されていますが、中でも53・67・79行目の [live_session](https://hexdocs.pm/phoenix_live_view/0.18.3/Phoenix.LiveView.Router.html#live_session/3) に注目します。前からある機能ではありますが、LiveViewのマウント時に実行される関数を指定できます。例えば

```elixir
live_session :require_authenticated_user,
  on_mount: [{SampleWeb.UserAuth, :ensure_authenticated}] do
  ...
end
```

という記述がある場合、ブロック内で指定したLiveViewの `mount/3` の前に `SampleWeb.UserAuth.on_mount/4` が実行されます。第一引数は `:ensure_authenticated` で、[該当のコード](https://github.com/mokichi/phx-gen-auth-1-7-sample/blob/378d0ba865801ca851520363c81af021d82031b0/lib/sample_web/user_auth.ex#L150-L163)を見ると「ログインしていなければLiveViewのマウントはされずにログイン画面にリダイレクトする」という動きになっています。

## アカウント登録

[lib/sample_web/live/user_registration_live.ex](https://github.com/mokichi/phx-gen-auth-1-7-sample/blob/main/lib/sample_web/live/user_registration_live.ex)

アカウント登録に成功後、63行目で `trigger_submit` をtrueにすることで [`phx-trigger-action` 属性が働き](https://hexdocs.pm/phoenix_live_view/form-bindings.html#submitting-the-form-action-over-http)、 `/users/log_in?_action=registered` 宛にForm Submitが発生します。なぜこんな回りくどいことを...と思いつつも、ログインを見てみましょう。

## ログイン

[lib/sample_web/live/user_login_live.ex](https://github.com/mokichi/phx-gen-auth-1-7-sample/blob/main/lib/sample_web/live/user_login_live.ex)

ログイン処理がどこにもないぞ...？と最初は思いましたが、18-40行目をよ〜く見てみると、 `simple_form` コンポーネントは使われていますが実態としては普通のformタグになります。そのため、「Sign in」ボタンを押すと `/users/log_in` 宛にForm Submitが発生し、 `SampleWeb.UserSessionController#create/2` が呼ばれます。

[lib/sample_web/controllers/user_session_controller.ex](https://github.com/mokichi/phx-gen-auth-1-7-sample/blob/main/lib/sample_web/controllers/user_session_controller.ex) の27行目がセッションにアクセストークンを入れているコードになります。なぜこんな回りくどい仕組みになっているかというと、おそらくですが、LiveViewとController（非LiveView）の両方で同一のログイン状態を扱うためのつくりなんだろうな〜と思っています。

## メールアドレス/パスワード変更

[lib/sample_web/live/user_settings_live.ex](https://github.com/mokichi/phx-gen-auth-1-7-sample/blob/main/lib/sample_web/live/user_settings_live.ex)

1画面に複数のフォームがありますが、ここは普通のLiveView実装ですね。特に気になるところはありません。

## パスワードリセット

[lib/sample_web/live/user_forgot_password_live.ex](https://github.com/mokichi/phx-gen-auth-1-7-sample/blob/main/lib/sample_web/live/user_forgot_password_live.ex)

ここも普通のLiveView実装ですが、パスワードリセットの流れは

1. パスワードリセット対象アカウントのメールアドレスを入力
1. 届いたメール本文中のURLにアクセス
1. [lib/sample_web/live/user_reset_password_live.ex](https://github.com/mokichi/phx-gen-auth-1-7-sample/blob/main/lib/sample_web/live/user_reset_password_live.ex) で新しいパスワードを設定

というよくある流れです。パスワードリセットが完了するとログイン画面に遷移してログインするように促されますが、単にリダイレクトするだけなので特別なことはないですね。

# 最後に

私の観測範囲内では、認証ライブラリというとブラックボックスっぽくなっていてカスタマイズするのに苦労するものが多い印象ですが、phx.gen.authはコードジェネレータなので実装を追いやすくカスタマイズも自由にできます。以前仕事でphx.gen.authを使ったときはLiveView化する余裕はありませんでしたが、今後はLiveViewアプリ開発がより一層捗りそうです :smirk: