Doorman

Modules and functions to make authentication with Plug/Phoenix and Ecto easy without tons of configuration or boxing users into rigid framework.

You can find more in-depth documentation here.

Installation

Add doorman to your dependencies in mix.exs.

def deps do
  [{:doorman, "~> 0.0.4"}]
end

Then add the configuration to config/config.exs.

config :doorman,
  repo: MyApp.Repo,
  user_module: MyApp.User,

Phoenix Quick Start

First generate a user model with a hashed_password field.

$ mix ecto.gen.model User users email hashed_password 

Next, use Doorman.Auth.Bcrypt in your new User module and add a virtual password field. hash_password/1 is used in the changeset to hash our password and put it into the changeset as hashed_password.

defmodule MyApp.User do
  use MyApp.Web, :model
  use Doorman.Auth.Bcrypt

  schema "users" do
    field :email, :string
    field :hashed_password, :string
    field :password, :string, virtual: true

    timestamps
  end

  def create_changeset(struct, params \\ %{}) do
    struct
    |> cast(params, ~w(email password))
    |> hash_password
  end
end

Finally, we can add our plug so we can have access to current_user on conn.assigns. A login strategy must to be passed in as an argument so Doorman can find the current user.

plug Doorman.Login.Session

Creating Users

To create a user we can use the MyApp.create_changeset/2 function we defined.

defmodule MyApp.UserController do
  alias MyApp.User

  def new(conn, _params) do
    changeset = User.create_changeset(%User{})
    conn |> render("new.html", changeset: changeset)
  end

  def create(conn, %{"user" => user_params}) do
    changeset = User.create_changeset(%User{}, user_params)

    case Repo.insert(changeset) do
      {:ok, user} ->
        conn |> redirect(to: "/")
      {:error, changeset} ->
        conn |> render("new.html", changeset: changeset)
    end
  end
end

Logging in users

To accept logins we can use Doorman.Login.Session.login/2.

defmodule MyApp.SessionController do
  import Doorman.Login.Session, only: [login: 2]

  def create(conn, %{"email" => email, "password" => "password"})
    if user = MyApp.Repo.get_by(MyApp.User, email: email) do
      if MyApp.User.authenticate(user, password) do
        conn
        |> login(user) # Sets :user_id on conn's session
        |> put_flash(:notice, "Successfully logged in")
        |> redirect(to: "/")
      else
        conn
        |> put_flash(:error, "Password was incorrect")
        |> render "new.html"
      end
    else
      conn
      |> put_flash(:error, "No account for provided email found")
      |> render "new.html"
    end
  end
end

Requiring Authentication

To require a user to be authenticated the Doorman.RequireLogin plug can be used. It requires a function to be passed to it in order to handle unauthenticated requests.

plug Doorman.RequireLogin, &redirect_to_login/1

defp redirect_to_login(conn) do
  conn |> redirect(to: session_path(conn, :new)) |> Plug.Conn.halt
end