はじめに
この記事ではPhoenixのバージョン1.7.0を使用しています。
$ mix phx.new -v
Phoenix installer v1.7.0
前回の記事に引き続き、今回はアカウントのログインを見ていきます。
以下はこのシリーズの目次です。
- PhoenixLiveViewを使用したmix phx.gen_authを詳しく見てみた【登録〜認証編】①
- PhoenixLiveViewを使用したmix phx.gen_authを詳しく見てみた【認証メール再送信編】②
- PhoenixLiveViewを使用したmix phx.gen_authを詳しく見てみた【ログイン編】③
目次
生成されたファイルの確認
前回はアカウントの認証メールを再送信するための機能を確認しました。
今回はログインをするまでの生成されたファイルを確認します。
ログイン
ログインをするためのurlは以下です。
http://localhost:4000/accounts/log_in
ログインに該当するソースコードは以下です。
defmodule AuthSampleWeb.AccountLoginLive do
use AuthSampleWeb, :live_view
def render(assigns) do
~H"""
<div class="mx-auto max-w-sm">
<.header class="text-center">
Sign in to account
<:subtitle>
Don't have an account?
<.link navigate={~p"/accounts/register"} class="font-semibold text-brand hover:underline">
Sign up
</.link>
for an account now.
</:subtitle>
</.header>
<.simple_form for={@form} id="login_form" action={~p"/accounts/log_in"} phx-update="ignore">
<.input field={@form[:email]} type="email" label="Email" required />
<.input field={@form[:password]} type="password" label="Password" required />
<:actions>
<.input field={@form[:remember_me]} type="checkbox" label="Keep me logged in" />
<.link href={~p"/accounts/reset_password"} class="text-sm font-semibold">
Forgot your password?
</.link>
</:actions>
<:actions>
<.button phx-disable-with="Signing in..." class="w-full">
Sign in <span aria-hidden="true">→</span>
</.button>
</:actions>
</.simple_form>
</div>
"""
end
def mount(_params, _session, socket) do
email = live_flash(socket.assigns.flash, :email)
form = to_form(%{email: email}, as: "account")
{:ok, assign(socket, form: form), temporary_assigns: [form: form]}
end
end
まず、mount
を確認します。
def mount(_params, _session, socket) do
email = live_flash(socket.assigns.flash, :email)
form = to_form(%{email: email}, as: "account")
{:ok, assign(socket, form: form), temporary_assigns: [form: form]}
end
まずlive_flash/2
でflash
にアサインされている:email
キーの値を取得します。
続いて取得したemailを使用したPhoenix.HTML.Form
構造体を作成します。
そして作成したPhoenix.HTML.Form
構造体をアサインしています。
mount
の返り値で指定しているキーワードtemporary_assigns
はレンダリングの度にその値をリセットする必要がある、アサインされているキーを指定します。値がリセットされると、明示的に割り当てられるまで再レンダリングはしません。
ソケットにform
キーでアサインされている値は以下です。
%Phoenix.HTML.Form{
source: %{email: nil},
impl: Phoenix.HTML.FormData.Map,
id: "account",
name: "account",
data: %{},
hidden: [],
params: %{email: nil},
errors: [],
options: [],
index: nil,
action: nil
}
次にrender
を確認します。
def render(assigns) do
~H"""
<div class="mx-auto max-w-sm">
<.header class="text-center">
Sign in to account
<:subtitle>
Don't have an account?
<.link navigate={~p"/accounts/register"} class="font-semibold text-brand hover:underline">
Sign up
</.link>
for an account now.
</:subtitle>
</.header>
<.simple_form for={@form} id="login_form" action={~p"/accounts/log_in"} phx-update="ignore">
<.input field={@form[:email]} type="email" label="Email" required />
<.input field={@form[:password]} type="password" label="Password" required />
<:actions>
<.input field={@form[:remember_me]} type="checkbox" label="Keep me logged in" />
<.link href={~p"/accounts/reset_password"} class="text-sm font-semibold">
Forgot your password?
</.link>
</:actions>
<:actions>
<.button phx-disable-with="Signing in..." class="w-full">
Sign in <span aria-hidden="true">→</span>
</.button>
</:actions>
</.simple_form>
</div>
"""
end
ここで確認すべきはフォームの送信先は/accounts/log_in
ということです。今回はphx-submit
が指定されていないのでAccountLoginLive
のイベントが呼ばれないということです。ではどこに送信するかというと、AccountSessionController
のcreate
アクションです。
ではAccountSessionController
のcreate
アクションを確認します。
defmodule AuthSampleWeb.AccountSessionController do
use AuthSampleWeb, :controller
alias AuthSample.Accounts
alias AuthSampleWeb.AccountAuth
def create(conn, %{"_action" => "registered"} = params) do
create(conn, params, "Account created successfully!")
end
def create(conn, %{"_action" => "password_updated"} = params) do
conn
|> put_session(:account_return_to, ~p"/accounts/settings")
|> create(params, "Password updated successfully!")
end
def create(conn, params) do
create(conn, params, "Welcome back!")
end
defp create(conn, %{"account" => account_params}, info) do
%{"email" => email, "password" => password} = account_params
if account = Accounts.get_account_by_email_and_password(email, password) do
conn
|> put_flash(:info, info)
|> AccountAuth.log_in_account(account, account_params)
else
# In order to prevent user enumeration attacks, don't disclose whether the email is registered.
conn
|> put_flash(:error, "Invalid email or password")
|> put_flash(:email, String.slice(email, 0, 160))
|> redirect(to: ~p"/accounts/log_in")
end
end
def delete(conn, _params) do
conn
|> put_flash(:info, "Logged out successfully.")
|> AccountAuth.log_out_account()
end
end
create
アクションの関数が3つありますが、今回呼ばれるのは以下です。
def create(conn, params) do
create(conn, params, "Welcome back!")
end
では、create/3
関数を呼び出しているのでこちらも確認します。
defp create(conn, %{"account" => account_params}, info) do
%{"email" => email, "password" => password} = account_params
if account = Accounts.get_account_by_email_and_password(email, password) do
conn
|> put_flash(:info, info)
|> AccountAuth.log_in_account(account, account_params)
else
# In order to prevent user enumeration attacks, don't disclose whether the email is registered.
conn
|> put_flash(:error, "Invalid email or password")
|> put_flash(:email, String.slice(email, 0, 160))
|> redirect(to: ~p"/accounts/log_in")
end
end
まず、Accounts.get_account_by_email_and_password/2
関数にメールアドレスとパスワードを渡してアカウントを取得します。
ここで取得できなかった場合はput_flash/3
でエラー時のメッセージとフォームから受け取ったメールアドレスを保持させます。その後/accounts/log_in
(AccountLoginLive
のmount
を呼びます)にリダイレクトします。
ではアカウントを取得できた時はput_flash/3
で引数で受け取ったメッセージを保持させます。その後、AccountAuth.log_in_account/3
を呼びアカウントのログイン処理をします。
defmodule AuthSampleWeb.AccountAuth do
# ・・・(省略)
@max_age 60 * 60 * 24 * 60
@remember_me_cookie "_auth_sample_web_account_remember_me"
@remember_me_options [sign: true, max_age: @max_age, same_site: "Lax"]
# ・・・(省略)
def log_in_account(conn, account, params \\ %{}) do
token = Accounts.generate_account_session_token(account)
account_return_to = get_session(conn, :account_return_to)
conn
|> renew_session()
|> put_token_in_session(token)
|> maybe_write_remember_me_cookie(token, params)
|> redirect(to: account_return_to || signed_in_path(conn))
end
defp maybe_write_remember_me_cookie(conn, token, %{"remember_me" => "true"}) do
put_resp_cookie(conn, @remember_me_cookie, token, @remember_me_options)
end
defp maybe_write_remember_me_cookie(conn, _token, _params) do
conn
end
defp renew_session(conn) do
conn
|> configure_session(renew: true)
|> clear_session()
end
# ・・・(省略)
defp put_token_in_session(conn, token) do
conn
|> put_session(:account_token, token)
|> put_session(:live_socket_id, "accounts_sessions:#{Base.url_encode64(token)}")
end
# ・・・(省略)
defp signed_in_path(_conn), do: ~p"/"
end
まず、Accounts.generate_account_session_token
でログイン用のトークンを作成します。
続いて、renew_session/1
で一度セッションをクリアしています。
次に、put_token_in_session/1
でセッションに:account_token
で生成したトークン、:live_socket_id
で生成したトークンをBase64でエンコードした値を保持させています。
次にmaybe_write_remeber_me_cookie/3
でログイン状態を保つかをチェックします。
ログイン状態を保つ場合は、put_resp_cookie/4
でCookieに値を証明します。
ログイン状態を保たない場合は何もしません。
最後にリダイレクトをします。
リダイレクト先はセッションにログイン後のurlの情報があればそのurlにリダイレクトします。情報がない場合は/
にリダイレクトします。
終わり
今回もここまで読んで頂きありがとうございました。
次回はパスワードリセットの部分について見ていきます。