①Elixirユーザ認証ライブラリ「phx_gen_auth」の本番向け改造点：ユーザ登録時等のメール通知追加、メール本文のカスタマイズ

[Elixir Digitalization Implementors／fukuoka.ex／kokura.ex](https://fukuokaex.connpass.com/)のpiacereです
ご覧いただいて、ありがとうございます :bow:

Phoenixにユーザ認証を導入してくれる「phx_gen_auth」は、そのまま使っても、機能的には支障が無い完成度ですが、プロダクション利用時は、見た目やメッセージ、開放許可する機能がデフォルトのままではダメなケースも多いかと思います

そこをプロダクション向きにカスタマイズするポイントを、何回かに分けて解説しようと思います

<h3>:ocean::ocean: Elixir ranked second on the Qiita Advent calendar :ocean::ocean:</h3>

In the programming language category ranking, Rust was 1st, Elixir was 2nd, Golang was 3rd, and all were modern programming languages.:laughing:
https://qiita.com/advent-calendar/2020/elixir
[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/ea185203-c525-c778-dc10-6d2ecc391616.png)](https://qiita.com/advent-calendar/2020/elixir)

In addition, our Elixir community "fukuoka.ex" has won the top spot in the Web Technology category.
https://qiita.com/advent-calendar/2020/fukuokaex
[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/8a22a81f-2917-6072-10bd-525e0fc24b75.png)](https://qiita.com/advent-calendar/2020/neosvr_reso)

# 本コラムの検証環境

本コラムは、以下環境で検証しています（Windowsで実施していますが、Linuxやmacでも動作する想定です）

- Windows 11
- Elixir 1.12.0 on WSL2 Ubuntu　※<b>[最新版のインストール手順はコチラ](https://qiita.com/piacerex/items/6b64dfe70f48c89d8385)</b>
- Phoenix 1.6.0　　　　　　　　※<b>[最新版のインストール手順はコチラ](https://qiita.com/piacere_ex/items/b7787580fce5f148242f#phoenix%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)</b>
- PostgreSQL 10.17　　　　　　※<b>[最新版のインストール手順はコチラ](https://qiita.com/piacere_ex/items/a7558adc6856e3577dc6#postgresql%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)</b>

なお、本コラム内で扱うPhoenix PJ名は「basic」を前提にしています

# どんな風に変わるのか？

phx_gen_authのデフォルトは、以下のような感じで、見た目やメッセージ、バリデーション、あと開放許可する機能が、必ずしもプロダクション向きとは限りません
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/7855700f-e7cd-5db7-c826-4b1d17a52307.png)

これを、下記のような感じに仕立てることができます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/fa5ac26c-be1f-bed5-e9dc-ea6e5e3b66c6.png)

それでは、順を追って、改造ポイントを挙げていきますが、今回は、見た目や日本語化では無く、プロダクション化の際には一番手で必須になるであろう「メール通知の追加」です

# 事前準備：Phoenix PJ作成、phx_gen_auth導入

<font color="red">【2021/09/25追加】
Phoenix 1.6では、phx_gen_authが標準搭載となり、ライブラリ追加不要となりました</font>

<font color="red">【2021/09/25追加】
Phoenix 1.6では、LiveViewが標準搭載となり、「--live」不要となりました（逆に不要時は「--no-live」を指定するようになりました）</font>

Phoenix 1.6以降であれば、特に何も指定せず、Phoenix PJを作成します

```
> mix phx.new basic
```

Phoenix 1.5であれば、「--live」を付けて、PJを作成します

```
> mix phx.new basic --live
```

Phoenix 1.5時のみ、phx_gen_authを外部ライブラリとして追加します

```elixir:mix.exs
defmodule Basic.MixProject do
…
  defp deps do
    [
      {:phx_gen_auth, "~> 0.6"},
…
```

認証モジュールの追加を行います

```
> mix deps.get
> mix phx.gen.auth Accounts Account accounts
```

認証モジュール追加後、新たなライブラリ追加が自動的に行われるので、再度、ライブラリインストールを行い、DB作成／マイグレートし、Phoenixを起動します

```
> mix deps.get
> mix ecto.create
> mix ecto.migrate
> mix phx.server
```

``http://localhost:4000`` にアクセスして、右上に「Log in」リンク等が追加されていることを確認してください

# ①メール通知追加（ユーザ登録時／パスワードリセット時／メールアドレス変更時）

phx_gen_authは、ユーザ認証に必要なメール送付のコード挿入ポイントを明確にしていますが、実際にメールを送信する実装が入っていないため、これを各自で準備する必要があります

## ①－１：デフォルトのメール送付内容（ただしメール送信化されていない）をログで確認

<font color="red">【2021/09/25追加】
Phoenix 1.6に標準搭載のphx_gen_authでは、下記ログが出力されなくなりました</font>

メール送付内容は、ユーザ登録時やパスワードリセット、メールアドレス変更を実行した際、Phoenix起動後のログとして追うことができます

```
iex> [info] POST /accounts/register
iex> [debug] Processing with BasicWeb.AccountRegistrationController.create/2
…
iex> [debug] QUERY OK source="accounts" db=0.0ms idle=547.0ms
SELECT TRUE FROM "accounts" AS u0 WHERE (u0."email" = $1) LIMIT 1 ["xxxxxx@xxxxxx.xxx"]
iex> [debug] QUERY OK db=0.0ms idle=813.0ms
INSERT INTO "accounts" ("email","hashed_password","inserted_at","updated_at") VALUES ($1,$2,$3,$4) RETURNING "id" ["xxxxxx@xxxxxx.xxx", "$pbkdf2-sha512$XXXXXX$XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", ~N[2021-04-06 09:44:06], ~N[2021-04-06 09:44:06]]
iex> [debug] QUERY OK db=0.0ms idle=813.0ms
INSERT INTO "accounts_tokens" ("context","sent_to","token","account_id","inserted_at") VALUES ($1,$2,$3,$4,$5) RETURNING "id" ["confirm", "xxxxxx@xxxxxx.xxx", <<XXX, XXX, XXX, XXX, XXX, XXX, XXX, XXX, XXX, XXX, XXX, XXX, XXX, XXX, XXX>>, 4, ~N[2021-04-06 09:44:06]]
iex> [debug]
==============================

Hi xxxxxx@xxxxxx.xxx,

You can confirm your account by visiting the URL below:

https://xxxxx.xxxx/accounts/confirm/ihOpbTDv2-SKcFLM9_rVs-2Wt_I-YNOCjGMKilBntRg

If you didn't create an account with us, please ignore this.

==============================
```

デフォルトではメール送信に対応していないため、自分でメール送信ライブラリを導入し、メール送信を実装する必要があります

## ①－２：メール送信ライブラリ導入

<font color="red">【2021/09/25追加】
Phoenix 1.6では、メール送信ライブラリとして「Swoosh」が標準で入り、下記コード中にあったログ出力も削除されました</font>

メール送信は、下記コードで実装されています

```elixir:lib/basic/accounts/account_notifier.ex
defmodule Basic.Accounts.AccountNotifier do
…
  defp deliver(recipient, subject, body) do
    email =
      new()
      |> to(recipient)
      |> from({"MyApp", "contact@example.com"})
      |> subject(subject)
      |> text_body(body)

    with {:ok, _metadata} <- Mailer.deliver(email) do
      {:ok, email}
    end
  end
…
```

まずはメール送信ライブラリを導入します

下記、「①－２－１：Swoosh導入」か「①－２－２：Bamboo導入」のいずれかを実施してください

## ①－２－１：Swoosh導入

本体ライブラリは、Phoenix 1.6の場合、標準でバンドルされているため、SMTP用アダプターのみ追加します

```elixir:mix.exs
defmodule Basic.MixProject do
  use Mix.Project
…
  defp deps do
    [
      {:gen_smtp, "~> 1.1"}, # <-- add here
…
```

ライブラリをインストールします

```
mix deps.get
```

## ①－２－２：Bamboo導入

本体ライブラリと、SMTP用アダプターを追加します

```elixir:mix.exs
defmodule Basic.MixProject do
  use Mix.Project
…
  defp deps do
    [
      {:bamboo, "~> 2.2"}, # <-- add here
      {:bamboo_smtp, "~> 4.1"}, # <-- add here
…
```

ライブラリをインストールします

```
mix deps.get
```

メール送信主体となるモジュールをlib配下の任意の場所に作ります

```elixir:lib/util/mailer.ex
defmodule Basic.Mailer do
  use Bamboo.Mailer, otp_app: :basic
end
```

## ①－３：メール送信設定

config.exsにSMTPメールサーバ情報を設定します

うち、``「port」``は、SMTPサーバ毎に異なり、間違っていると、タイムアウトするまでメール送信が終わらない状態になるので、確認の上、設定してください

なお今回の例では、config.exsに直接パスワードやSMTPサーバを記載していますが、セキュリティを考慮する場合、環境変数に設定し、Application.get_env()で読み込む等の工夫をするのもアリです

下記は、「①－３－１：Swooshにおけるメール送信設定」か「①－３－２：Bambooにおけるメール送信設定」のいずれかを実施してください

## ①－３－１：Swooshにおけるメール送信設定

```elixir:config/config.exs
…
config :basic, Basic.Mailer, adapter: Swoosh.Adapters.SMTP,
  relay: "【SMTPサーバのドメイン名 or IPアドレス】",
  username: "【SMTPサーバのログインユーザID】",
  password: "【SMTPサーバのログインパスワード】",
  port: 【SMTPサーバのポート番号（SSL有と無で異なるケースあるので要注意）】
```

Phoenixを起動します

```
mix phx.server
```

## ①－３－２：Bambooにおけるメール送信設定

```elixir:config/config.exs
…
config :basic, Basic.Mailer, adapter: Bamboo.SMTPAdapter,
  server: "【SMTPサーバのドメイン名 or IPアドレス】",
  username: "【SMTPサーバのログインユーザID】",
  password: "【SMTPサーバのログインパスワード】",
  port: 【SMTPサーバのポート番号（SSL有と無で異なるケースあるので要注意）】
```

Phoenixを起動します

```
mix phx.server
```

## ①－４：メールfrom、subject、body設定

下記コードのfrom、subject、bodyを変更します

```elixir:lib/basic/accounts/account_notifier.ex（変更後）
defmodule Basic.Accounts.AccountNotifier do
…
  defp deliver(recipient, subject, body) do
    email =
      new()
      |> to(recipient)
      |> from({"【from表示名】", "【fromメールアドレス】"})
      |> subject("【件名】")
      |> text_body("【本文】")

    with {:ok, _metadata} <- Mailer.deliver(email) do
      {:ok, email}
    end
  end
…
```

これで、ユーザ登録時／パスワードリセット時／メールアドレス変更時の全てで、メール通知されるようになるので、動かして試してください

なお、一発で上手くいかない場合は、``new() |> Mailer.deliver(email)``の部分をiexで手動で叩いて確認すると良いかも知れません

## ①－５：機能毎のsubject、body設定

さて、ここまででメールは飛ぶようになったのですが、本文がこのままだとプロダクションっぽく無いので、本文をカスタマイズします

上記と同じファイルの、以下箇所に本文テンプレートがあるので、ここをいじってください

各関数の#{url}は、メール本文中で、下記表のような用途のURLに置換されます

関数名 | 何のためのURLか？
-|-
deliver_user_confirmation_instructions | 仮登録から本登録への移行を受け付けるURL
deliver_user_reset_password_instructions | パスワードリマインダのパスワードリセットページを表示するURL
deliver_user_update_email_instructions | メールアドレス変更を受け付けるURL

コードは下記の通りです

```elixir:lib/basic/accounts.ex（変更前）
defmodule Basic.Accounts do
…
  @doc """
  Deliver instructions to confirm account.
  """
  def deliver_user_confirmation_instructions(account, url) do
    deliver(account.email, "Confirmation instructions", """

    ==============================

    Hi #{account.email},

    You can confirm your account by visiting the URL below:

    #{url}

    If you didn't create an account with us, please ignore this.

    ==============================
    """)
  end

  @doc """
  Deliver instructions to reset a account password.
  """
  def deliver_user_reset_password_instructions(account, url) do
    deliver(account.email, "Reset password instructions", """

    ==============================

    Hi #{account.email},

    You can reset your password by visiting the URL below:

    #{url}

    If you didn't request this change, please ignore this.

    ==============================
    """)
  end

  @doc """
  Deliver instructions to update a account email.
  """
  def deliver_user_update_email_instructions(account, url) do
    deliver(account.email, "Update email instructions", """

    ==============================

    Hi #{account.email},

    You can change your email by visiting the URL below:

    #{url}

    If you didn't request this change, please ignore this.

    ==============================
    """)
  end
end
```

たとえば、パスワードリマインダのメールをカスタマイズするなら、下記のような感じで、subjectとbodyを変更します

```elixir:lib/basic/accounts.ex（変更前）
defmodule Basic.Accounts do
…
  @doc """
  Deliver instructions to reset a account password.
  """
  def deliver_user_reset_password_instructions(account, url) do
    deliver(account.email, "Please change your password", """

    //////////////////////////////////

    Hi #{account.email},

    You can reset your password by visiting the URL below:

    #{url}

    If you didn't request this change, please ignore this.

    //////////////////////////////////
    """)
  end
…
```

なお、本文を外部テンプレート.eexファイルに切り出して、ファイル読込するようにし、これら関数に渡すと、運用時の再リリースや、ソースコード直接編集が不要になるのでオススメです

# 最後に

今回は、ユーザ認証ライブラリ「phx_gen_auth」のプロダクション向け改造ポイントのうち、メール通知の追加について解説しました

メール通知は、phx_gen_auth導入直後、使えない状態でセットアップされるため、今回コラムで行った手順は、phx_gen_authでプロダクション開発する際は、最重要のポイントになるかと思います

[次回は、バリデーションのカスタマイズ](https://qiita.com/piacerex/items/5f11e33db748830c7636)をします

なお、気が向いたら、上記を全てセッティングした会員制WebサイトのテンプレートをOSS化したいなぁって思っています … つまり、Elixir／Phoenixであれば、会員制Webサイトの構築をイチからやらなくて良くなります