見出し画像

ゼロからElixir+Phoenixに入門する(2)

技術ブログからのお引越し(2020/1/24投稿)です。

こんにちは、空のもりわきです。
前回から引き続き、elixirとphoenixを使った管理画面を作っていきます。

認証処理

今回はguardianを利用して認証を行っていきたいと思います。
mix.exs に以下を追加します。argon2_elixirはパスワードのハッシュアルゴリズムとして利用します。

mix.exs に以下を追加します。argon2_elixirはパスワードのハッシュアルゴリズムとして利用します。

# mix.exs
defp deps do
 [ 
   ...
   {:guardian, "~> 2.0"},
   {:argon2_elixir, "~> 2.0"}
 ]
end

deps.getでパッケージをインストールします。

$ mix deps.get

ここからはguardianの初期設定を行っていきます。(公式 readmeも参照してください)

# config/config.exs
config :phoenix_admin, PhoenixAdmin.Accounts.Guardian,
 issuer: "phoenix_admin",
 secret_key: "aaaaaaaaaaa"  # mix phx.gen.secret で作った値を入れます

次にguardianを扱うためのモジュールを用意します。
ここで定義されている2つのメソッドはguardianに用意されているコールバックであり、subject_for_tokenでユーザ情報を取得し、resource_from_claimsでユーザ情報をusersテーブルから取得します。
subject_for_tokenで返却したsubにidが入っており、resource_from_claimsの引数claimsでsubject_for_tokenの情報にアクセスできるようになります。

ユーザを作成した際にhash化を行う処理(put_password_hash)をuser.exに加えます。
また、追加したput_password_hashが呼ばれるようにchangesetもあわせて編集します。

コントローラーのcreateアクションにGuardianでサインインするように処理を追加します。これでユーザ登録した時点でログイン状態とすることができます。

パイプライン

認証エラーが発生した場合のエラーハンドラを作成しておきます。

続いてパイプラインを作成します。
パイプラインはプラグインを順序をつけて実行できるようなものです。
後ほどrouter.exで利用することになりますが、ルーティングの際にこのパイプラインを事前に走らせることで認証チェックを行い、ログイン中のユーザのみ表示したいコンテンツを出し分けることができます。
Railsでいうbefore_action的な処理をまとめて定義して、ルーティングと一緒に処理できるというイメージでよいかと思います。

ログイン画面

ログインするためのコントローラーを作ります。
session_controller.exという名前で作成します。

・newアクションでは画面描画のみ実施します
・loginアクションでログイン処理を行います
 POSTされるパラメータは以下のフォーマットじゃないとloginアクションは発火されません。
 user[email] = メールアドレス
 user[password] = パスワード

session_controller用のviewとtemplateを用意しておきます。
phoenixではコントローラーと対にになるviewが必要で、このviewを経由してtemplateがレンダリングされるようです。
今のところviewは何もしていませんが(裏側ではPhoenix.Viewのrender/3が呼ばれ、templateが描画される)、templateに渡したい変数をviewでメソッドとして定義することができます。このあたりはヘルパーのような動きになりそうですね。

ルーティング

先程作ったパイプラインを auth_user という名前で定義します。
同時に、guardianで定義されているEnsureAuthenticatedをensure_authで定義します。
正確にログインしているユーザを判別するときはEnsureAuthenticatedを、ログインしてないかもしれない場合は自作したPipelineで判断するようです。

pipeline :auth_user do
  plug PhoenixAdmin.Accounts.Pipeline
end

pipeline :ensure_auth do
  plug Guardian.Plug.EnsureAuthenticated
end

# 未ログイン
scope "/", PhoenixAdminWeb do
  pipe_through [:browser, :auth_user]
  get "/", PageController, :index
  resources "/users", UserController, only: [:new, :create]
  get "/login", SessionController, :new
  post "/login", SessionController, :login
end

# ログイン済み
scope "/", PhoenixAdminWeb do
  pipe_through [:browser, :auth_user, :ensure_auth]
  resources "/users", UserController, except: [:new, :create]
  delete "/logout", SessionController, :logout
end

未ログイン状態では、ログインとユーザ登録のみ可能としています。
ログイン済状態では、ログアウトおよびユーザ一覧の照会と編集が可能になります(本当は自分のユーザだけ編集できる方がよいです)。

ログイン状態で表示の出し分けをする

未ログインなら「ログイン」、ログイン済みなら「ログアウト」のリンクをヘッダーにつけてみます。
ここでは現在ログインしているユーザ情報を取得し、その存在有無で出し分けをすることにします。
こちらの記事を参考に、新たにCurrentUserモジュールを作成してGuardian.Plug.current_resourceから取得したユーザ情報をセットするようにします。
Plug.Conn.assignによってテンプレート内で @current_userで参照できます。

このCurrentUserをrouter.exに先程用意した auth_user の中に追記します。
これにより、pipe_throughで定義したauth_userが呼ばれた段階でCurrentUserの処理が走ります。

pipeline :auth_user do
  plug PhoenixAdmin.Accounts.Pipeline
  plug PhoenixAdmin.Accounts.CurrentUser  # 追加
end

template/layout/app.html.eex内のヘッダに出し分けを入れて終わりです。
ちなみにユーザ名を表示する場合は @current_user.email で参照可能です。

<%= if @current_user do
 link "Logout", to: Routes.session_path(@conn, :logout), method: :delete
else
 link "Login", to: Routes.session_path(@conn, :login)
end %>

mix phx.server を実施し、localhost:4000/users を見てみましょう。

unauthenticated

と出ていると思います。未ログインでは見れないので当然ですね。

つぎにログイン画面を開いてみましょう。
localhost:4000/login

画像1

Sign upのリンク(localhost:4000/session/new)からユーザー登録してみます。

画像2

SAVEを押すとユーザ一覧(要認証)へリダイレクトされます。

画像3

ここからログアウトを押すことでログインページに戻ることも確認できました。

以上で管理画面の基本的な認証を行うことができました。

ここまで来れば商品管理機能は mix phx.gen.html でちゃちゃっと作れますね(というわけで以降割愛)。
今回はguardianのみを利用しましたが、別途 guardian_db を使ってトークンの状態管理(期限切れ等のチェック)を行うことができます。
もしプロダクションで利用する際はあわせて利用したほうがよさそうです。

まとめ

ElixirというよりPhoenixの記事になってしまいましたが、触ってみた感じとっつにきくさはあるもののこの程度の規模であればさくっと作ることができました。
やはりパターンマッチの存在が大きく、使いこなせるようになると書いてて楽しいんだろうなというのを実感しました。
とくに外部とAPI連携していてエラーレスポンスのパターンが多い場合は威力を発揮しそうです。昨年(2019年)Laprasさんで開催されたCrawlerNightでもクローラーはElixirで書くと捗るとおっしゃってました。

この記事が参加している募集

この記事が気に入ったらサポートをしてみませんか?