27
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ElixirAdvent Calendar 2021

Day 16

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

Last updated at Posted at 2021-04-06

Elixir Digitalization Implementors/fukuoka.ex/kokura.exのpiacereです
ご覧いただいて、ありがとうございます :bow:

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

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

:ocean::ocean: Elixir ranked second on the Qiita Advent calendar :ocean::ocean:

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

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

本コラムの検証環境

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

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

どんな風に変わるのか?

phx_gen_authのデフォルトは、以下のような感じで、見た目やメッセージ、バリデーション、あと開放許可する機能が、必ずしもプロダクション向きとは限りません
image.png

これを、下記のような感じに仕立てることができます
image.png

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

事前準備: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を外部ライブラリとして追加します

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

①-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」が標準で入り、下記コード中にあったログ出力も削除されました

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

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

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

下記、「①-2-1:Swoosh導入」か「①-2-2:Bamboo導入」のいずれかを実施してください

①-2-1:Swoosh導入

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

mix.exs
defmodule Basic.MixProject do
  use Mix.Project

  defp deps do
    [
      {:gen_smtp, "~> 1.1"}, # <-- add here

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

mix deps.get

①-2-2:Bamboo導入

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

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配下の任意の場所に作ります

lib/util/mailer.ex
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/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

①-3-2:Bambooにおけるメール送信設定

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

①-4:メールfrom、subject、body設定

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

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で手動で叩いて確認すると良いかも知れません

①-5:機能毎のsubject、body設定

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

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

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

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

コードは下記の通りです

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を変更します

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でプロダクション開発する際は、最重要のポイントになるかと思います

次回は、バリデーションのカスタマイズをします

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

27
6
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?