Elixir Digitalization Implementors/fukuoka.ex/kokura.exのpiacereです
ご覧いただいて、ありがとうございます
Phoenixにユーザ認証を導入してくれる「phx_gen_auth」は、そのまま使っても、機能的には支障が無い完成度ですが、プロダクション利用時は、見た目やメッセージ、開放許可する機能がデフォルトのままではダメなケースも多いかと思います
そこをプロダクション向きにカスタマイズするポイントを、何回かに分けて解説しようと思います

Elixir ranked second on the Qiita Advent calendar 
In the programming language category ranking, Rust was 1st, Elixir was 2nd, Golang was 3rd, and all were modern programming languages.
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
本コラムの検証環境
本コラムは、以下環境で検証しています(Windowsで実施していますが、Linuxやmacでも動作する想定です)
- Windows 11
- Elixir 1.12.0 on WSL2 Ubuntu ※最新版のインストール手順はコチラ
- Phoenix 1.6.0 ※最新版のインストール手順はコチラ
- PostgreSQL 10.17 ※最新版のインストール手順はコチラ
なお、本コラム内で扱うPhoenix PJ名は「basic」を前提にしています
どんな風に変わるのか?
phx_gen_authのデフォルトは、以下のような感じで、見た目やメッセージ、バリデーション、あと開放許可する機能が、必ずしもプロダクション向きとは限りません
それでは、順を追って、改造ポイントを挙げていきますが、今回は、見た目や日本語化では無く、プロダクション化の際には一番手で必須になるであろう「メール通知の追加」です
事前準備:Phoenix PJ作成、phx_gen_auth導入
【2021/09/25追加】
Phoenix 1.6では、phx_gen_authが標準搭載となり、ライブラリ追加不要となりました
【2021/09/25追加】
Phoenix 1.6では、LiveViewが標準搭載となり、「--live」不要となりました(逆に不要時は「--no-live」を指定するようになりました)
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を外部ライブラリとして追加します
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は、ユーザ認証に必要なメール送付のコード挿入ポイントを明確にしていますが、実際にメールを送信する実装が入っていないため、これを各自で準備する必要があります
①-1:デフォルトのメール送付内容(ただしメール送信化されていない)をログで確認
【2021/09/25追加】
Phoenix 1.6に標準搭載のphx_gen_authでは、下記ログが出力されなくなりました
メール送付内容は、ユーザ登録時やパスワードリセット、メールアドレス変更を実行した際、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.
==============================
デフォルトではメール送信に対応していないため、自分でメール送信ライブラリを導入し、メール送信を実装する必要があります
①-2:メール送信ライブラリ導入
【2021/09/25追加】
Phoenix 1.6では、メール送信ライブラリとして「Swoosh」が標準で入り、下記コード中にあったログ出力も削除されました
メール送信は、下記コードで実装されています
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
…
まずはメール送信ライブラリを導入します
下記、「①-2-1:Swoosh導入」か「①-2-2:Bamboo導入」のいずれかを実施してください
①-2-1:Swoosh導入
本体ライブラリは、Phoenix 1.6の場合、標準でバンドルされているため、SMTP用アダプターのみ追加します
defmodule Basic.MixProject do
use Mix.Project
…
defp deps do
[
{:gen_smtp, "~> 1.1"}, # <-- add here
…
ライブラリをインストールします
mix deps.get
①-2-2:Bamboo導入
本体ライブラリと、SMTP用アダプターを追加します
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配下の任意の場所に作ります
defmodule Basic.Mailer do
use Bamboo.Mailer, otp_app: :basic
end
①-3:メール送信設定
config.exsにSMTPメールサーバ情報を設定します
うち、「port」
は、SMTPサーバ毎に異なり、間違っていると、タイムアウトするまでメール送信が終わらない状態になるので、確認の上、設定してください
なお今回の例では、config.exsに直接パスワードやSMTPサーバを記載していますが、セキュリティを考慮する場合、環境変数に設定し、Application.get_env()で読み込む等の工夫をするのもアリです
下記は、「①-3-1:Swooshにおけるメール送信設定」か「①-3-2:Bambooにおけるメール送信設定」のいずれかを実施してください
①-3-1:Swooshにおけるメール送信設定
…
config :basic, Basic.Mailer, adapter: Swoosh.Adapters.SMTP,
relay: "【SMTPサーバのドメイン名 or IPアドレス】",
username: "【SMTPサーバのログインユーザID】",
password: "【SMTPサーバのログインパスワード】",
port: 【SMTPサーバのポート番号(SSL有と無で異なるケースあるので要注意)】
Phoenixを起動します
mix phx.server
①-3-2:Bambooにおけるメール送信設定
…
config :basic, Basic.Mailer, adapter: Bamboo.SMTPAdapter,
server: "【SMTPサーバのドメイン名 or IPアドレス】",
username: "【SMTPサーバのログインユーザID】",
password: "【SMTPサーバのログインパスワード】",
port: 【SMTPサーバのポート番号(SSL有と無で異なるケースあるので要注意)】
Phoenixを起動します
mix phx.server
①-4:メールfrom、subject、body設定
下記コードのfrom、subject、bodyを変更します
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で手動で叩いて確認すると良いかも知れません
①-5:機能毎のsubject、body設定
さて、ここまででメールは飛ぶようになったのですが、本文がこのままだとプロダクションっぽく無いので、本文をカスタマイズします
上記と同じファイルの、以下箇所に本文テンプレートがあるので、ここをいじってください
各関数の#{url}は、メール本文中で、下記表のような用途のURLに置換されます
関数名 | 何のためのURLか? |
---|---|
deliver_user_confirmation_instructions | 仮登録から本登録への移行を受け付けるURL |
deliver_user_reset_password_instructions | パスワードリマインダのパスワードリセットページを表示するURL |
deliver_user_update_email_instructions | メールアドレス変更を受け付けるURL |
コードは下記の通りです
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を変更します
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でプロダクション開発する際は、最重要のポイントになるかと思います
なお、気が向いたら、上記を全てセッティングした会員制WebサイトのテンプレートをOSS化したいなぁって思っています … つまり、Elixir/Phoenixであれば、会員制Webサイトの構築をイチからやらなくて良くなります