Phoenix 認証システム - mix phx.gen.auth

Phoenix では **mix phx.gen.auth** で、簡単に認証システムを作成し、既存システムに組み込んでいけます。この記事ではどのような認証システムが作成され、どのように利用できるのかを見ていきたいと思います。
[mix phx.gen.auth ドキュメント](https://hexdocs.pm/phoenix/mix_phx_gen_auth.html)

インターネット創世記の、昔々、ブログシステムを運営していた経緯から、PHPやNodejs、Meteor、Django、AWS JavaScriptなどでWebアプリを実験するときは、必ず認証システムを作成してました。もちろんちょっと前のPhoenixでもそうでした。それで言語・フレームワークの使い易さを計っていました。それが今では **mix phx.gen.auth** コマンド一発で可能なのはちょっと感動的です。


**【関連記事】**
[Phoenix で Blog を超簡単に作る - phx.gen.auth , phx.gen.html](https://qiita.com/sand/items/55a21235f8c2dfff51bd)
[Phoenix 認証システム - mix phx.gen.auth](https://qiita.com/sand/items/dda77cc3da3febf3c286)
[Phoenix LiveViewの基本設定 - mix phx.gen.schema](https://qiita.com/sand/items/5aea96852ceb2981021b)
[Phoenix1.6の基本的な仕組み - mix phx.gen.html](https://qiita.com/sand/items/f42dc35c47589eab687e)
[Elixir/Phoenix のシンプル認証 auth_plug](https://qiita.com/sand/items/160af41bd019e9c9f388)



# 1.前準備

プロジェクトを作成します。
```
 mix phx.new auth_test
cd auth_test
```


以下のコマンドを発行します。**Accounts context module** と **Accounts.User schema module** 、最後に schema moduleの複数形で **users table**が作成されます。さらに認証システムに必要なファイル一式が生成されます。
```
mix phx.gen.auth Accounts User users
```

以下のコマンドで生成されたファイルの設定を行います。

```
mix deps.get
mix ecto.setup
```

以上で前準備が整いました。

# 2. 認証システムにページを組み込む

**phx.gen.auth** で生成した認証システムに、新たに生成したページを **router.ex** 組み込み、どのように認証システムが働くかを確認します。**router.ex** には以下のような **plugs** が作成され追加されています。

* **fetch_current_user** - 可能ならば current user の情報を取得する
* **require_authenticated_user** - fetch_current_user で取得した user が存在し、認証されている必要がある
* **redirect_if_user_is_authenticated** - 認証済の user は利用できないページである

それぞれの定義は以下の **user_auth.ex** で確認できます。


```elixir:lib/auth_test_web/controllers/user_auth.ex
  ---
  @doc """
  Authenticates the user by looking into the session
  and remember me token.
  """
  def fetch_current_user(conn, _opts) do
    {user_token, conn} = ensure_user_token(conn)
    user = user_token && Accounts.get_user_by_session_token(user_token)
    assign(conn, :current_user, user)
  end

  defp ensure_user_token(conn) do
    if user_token = get_session(conn, :user_token) do
      {user_token, conn}
    else
      conn = fetch_cookies(conn, signed: [@remember_me_cookie])

      if user_token = conn.cookies[@remember_me_cookie] do
        {user_token, put_session(conn, :user_token, user_token)}
      else
        {nil, conn}
      end
    end
  end

  @doc """
  Used for routes that require the user to not be authenticated.
  """
  def redirect_if_user_is_authenticated(conn, _opts) do
    if conn.assigns[:current_user] do
      conn
      |> redirect(to: signed_in_path(conn))
      |> halt()
    else
      conn
    end
  end

  @doc """
  Used for routes that require the user to be authenticated.

  If you want to enforce the user email is confirmed before
  they use the application at all, here would be a good place.
  """
  def require_authenticated_user(conn, _opts) do
    if conn.assigns[:current_user] do
      conn
    else
      conn
      |> put_flash(:error, "You must log in to access this page.")
      |> maybe_store_return_to()
      |> redirect(to: Routes.user_session_path(conn, :new))
      |> halt()
    end
  end
  ---
```



それでは新たに、**hello.html.heex** を作成し、**router.ex** のそれぞれの箇所に組み込み、効果をみていきます。

```elixir:lib/auth_test_web/templates/page/hello.html.heex
<h1>Hello World !!!</h1>
<%= if @current_user do %>
  <h2><%= @current_user.email %></h2>
<% else %>
  <h2>Guest</h2>
<% end %>
```


**以下の実験を行う前に、ユーザ登録を行っておきます。**


## 2-1. pipe_through :browser

**router.ex** に "/hello" パスを挿入します。

```elixir:lib/auth_test_web/router.ex
  scope "/", AuthTestWeb do
    pipe_through :browser
    ---
    get "/hello", PageController, :hello  # ここに挿入
  end

```

**未ログイン** の状態で http://localhost:4000/hello にアクセスする。Guestが表示されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/7cf70fbd-d392-353d-81ae-205f64aacdbc.png)


**ログイン済** の状態で http://localhost:4000/hello にアクセスする。メールアドレスが表示されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/daa6c5f0-3aba-535b-11e8-ab87efa714eb.png)



## 2-2. pipe_through [:browser, :redirect_if_user_is_authenticated]

**router.ex** に "/hello" パスを挿入します。

```elixir:lib/auth_test_web/router.ex
  scope "/", AuthTestWeb do
    pipe_through [:browser, :redirect_if_user_is_authenticated]
    ---
    get "/hello", PageController, :hello  # ここに挿入
  end

```

**未ログイン** の状態で http://localhost:4000/hello にアクセスする。Guestが表示されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/f1064301-dd09-f6df-aa6d-c0238d947919.png)


**ログイン済** の状態で http://localhost:4000/hello にアクセスする。トップページに **リダイレクト** されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/982a2ecc-bc2e-4f48-7127-22ccb214b45b.png)


## 2-3. pipe_through [:browser, :require_authenticated_user]

**router.ex** に "/hello" パスを挿入します。

```elixir:lib/auth_test_web/router.ex
  scope "/", AuthTestWeb do
    pipe_through [:browser, :require_authenticated_user]
    ---
    get "/hello", PageController, :hello  # ここに挿入
  end
```

**未ログイン** の状態で http://localhost:4000/hello にアクセスする。ログイン画面にリダイレクトされました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/414d9732-fc1e-f43a-b4de-6463becc780a.png)


**ログイン済** の状態で http://localhost:4000/hello にアクセスする。メールアドレスが表示されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/daa6c5f0-3aba-535b-11e8-ab87efa714eb.png)



今回は以上です。

Phoenixの良いところは、生成された template などはカスタマイズして使うことが前提となっているので、字面の日本語化やカスタマイズなどは簡単に行えることかなと感じてます。