Pre configured Phoenix project with auth
Go to file
2019-12-11 14:48:47 +01:00
README.md Readme add 2019-12-11 14:48:47 +01:00

Auth tutorial for setting up basic user auth with put_session()

NOTE: PHOENIX_APP and PHOENIX_APP_WEB are just macros here, in your app user your apps name!

Deps used

# Admin auth
{:basic_auth, "~> 2.2.2"},
# Password hashing
{:bcrypt_elixir, "~> 2.0"}

Setup

Start off by running mix deps.get

User Schema

Make sure that you have some context that is like Accounts.User with email:unique and password_hash and that you have Accounts module with get_user!(id)... etc.

You can do this with

terminal:~/PHOENIX_PROJECT/$ mix phx.gen.html Accounts User users email:string:unique password_hash:string

Password hashing

After that open up accounts/user.ex and add line bellow to changeset()

def changeset(user, attrs) do
  ...
  |> update_change(:password_hash, &Bcrypt.hash_pwd_salt/1)
end

Auth helper

In PHOENIX_APP_WEB/ create folder /helpers and create file auth.ex

defmodule PHOENIX_APP_WEB.Helpers.Auth do
  import Plug.Conn, only: [get_session: 2]
  alias PHOENIX_APP.{Repo, Accounts.User}

  def signed_in?(conn) do
    user_id = get_session(conn, :current_user_id)
    if user_id, do: !!Repo.get(User, user_id)
  end
end

Then open your PHOENIX_APP_WEB.ex and inside add this import, we are doing this because we want to have this function in every view

def view do
  quote do
    ...
    import PHOENIX_APP_WEB.Helpers.Auth, only: [signed_in?: 1]
  end
end

Session templates/view/router/controller

Templates

Create /session folder in /templates and inside create 2 files sign-in.html.eex and sign-up.html.eex One will be used for Signing in and the other for Signing out

/session/sign-in.html
<%= form_for @conn, Routes.session_path(@conn, :create_session),[as: :session] , fn f -> %>

  <%= label f, :email %>
  <%= text_input f, :email %>
  <%= error_tag f, :email %>

  <%= label f, :password %>
  <%= password_input f, :password_hash %>
  <%= error_tag f, :password_hash %>

  <div>
    <%= submit "Sign in" %>
  </div>
<% end %>

/session/sign-up.html
<%= form_for @changeset, Routes.session_path(@conn, :create_user) , fn f -> %>
  <%= if @changeset.action do %>
    <div class="alert alert-danger">
      <p>Oops, something went wrong! Please check the errors below.</p>
    </div>
  <% end %>

  <%= label f, :email %>
  <%= text_input f, :email %>
  <%= error_tag f, :email %>

  <%= label f, :password %>
  <%= password_input f, :password_hash %>
  <%= error_tag f, :password_hash %>

  <div>
    <%= submit "Sign up" %>
  </div>
<% end %>

Views

Create session_view.ex in /views folder

defmodule PHOENIX_APP_WEB.SessionView do
  use PHOENIX_APP_WEB, :view
end

Router

under you scope "/" add following

scope "/", PHOENIX_APP_WEB do
  pipe_through :browser
  ...
  # Sign in
  get "/sign-in", SessionController, :sign_in
  post"/sign-in", SessionController, :create_session

  # Sign up
  get "/sign-up", SessionController, :sign_up
  post"/sign-up", SessionController, :create_user

  # Sign out
  post "/sign-out", SessionController, :sign_out
end

Controller

defmodule PHOENIX_APP_WEB.SessionController do
  use PHOENIX_APP_WEB, :controller

  alias PHOENIX_APP.Accounts
  alias Accounts.User

  def sign_in(conn, _params) do
    # If user is logged in and tries to connecto to "/sign-in" redirect him
    if is_logged?(conn) do
      redirect(conn, to: Routes.user_path(conn, :show))
    else
      render(conn, "sign-in.html")
    end
  end

  def sign_up(conn, _params) do
    # If user is logged in and tries to connecto to "/sign-up" redirect him
    if is_logged?(conn) do
      redirect(conn, to: Routes.user_path(conn, :show))
    else
      changeset = Accounts.change_user(%User{})
      render(conn, "sign-up.html", changeset: changeset)
    end
  end

  defp is_logged?(conn), do: !!get_session(conn, :current_user_id)

  def create_session(conn, %{"session" => auth_params} = _params) do
    user = Accounts.get_by_email(auth_params["email"])

    case Bcrypt.check_pass(user, auth_params["password_hash"]) do
      {:ok, user} ->
        conn
        |> put_session(:current_user_id, user.id)
        |> put_flash(:info, "Sign in, successful!")
        |> redirect(to: Routes.user_path(conn, :show))

      {:error, _} ->
        conn
        |> put_flash(:error, "Invalid e-mail/password. Try again!")
        |> redirect(to: Routes.session_path(conn, :sign_in))
    end
  end

  def create_user(conn, %{"user" => user_params}) do
    case Accounts.create_user(user_params) do
      {:ok, user} ->
        conn
        |> put_session(:current_user_id, user.id)
        |> put_flash(:info, "Sign up, successful!")
        |> redirect(to: Routes.user_path(conn, :show))

      {:error, %Ecto.Changeset{} = changeset} ->
        render(conn, "sign-up.html", changeset: changeset)
    end
  end

  def sign_out(conn, _params) do
    conn
    |> delete_session(:current_user_id)
    |> put_flash(:info, "Signed out.")
    |> redirect(to: Routes.page_path(conn, :index))
  end
end