Giter Site home page Giter Site logo

doorman's Introduction

Doorman

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

The primary goal of Doorman is to build an opinionated interface and easy to use API on top of flexible modules that can also be used directly.

You can find more in-depth documentation here.

Installation

Add doorman to your dependencies in mix.exs.

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

Then add the configuration to config/config.exs.

config :doorman,
  repo: MyApp.Repo,
  secure_with: Doorman.Auth.Bcrypt,
  user_module: MyApp.User

Phoenix Quick Start

First generate a user model with a hashed_password and session_secret field.

$ mix phoenix.gen.model User users email hashed_password session_secret

Please note: we recommend using citext (or equivalent for non-postgres databases) for the email column so that your email is case insensitive.

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
  import Doorman.Auth.Bcrypt, only: [hash_password: 1]

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

    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. 99% of the time that means adding the Doorman.Login.Session plug to your :browser pipeline:

pipeline :browser do
  # ...
  plug Doorman.Login.Session
  # ...
end

Creating Users

To create a user we can use the MyApp.create_changeset/2 function we defined. Here we'll also add the session_secret to the user, which is only needed when creating an user or in case of compromised sessions.

defmodule MyApp.UserController do
  use MyApp.Web, :controller
  alias Doorman.Auth.Secret
  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{}
      |> User.create_changeset(user_params)
      |> Secret.put_session_secret()

    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 login users we can use Doorman.authenticate and Doorman.Login.Session.login/2.

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

  def create(conn, %{"session" => %{"email" => email, "password" => password}}) do
    if user = Doorman.authenticate(email, password) do
      conn
      |> login(user) # Sets :user_id and :session_secret on conn's session
      |> put_flash(:notice, "Successfully logged in")
      |> redirect(to: "/")
    else
      conn
      |> put_flash(:error, "No user found with the provided credentials")
      |> render("new.html")
    end
  end
end

Requiring Authentication

To require a user to be authenticated you can build a simple plug around Doorman.logged_in?/1.

defmodule MyApp.RequireLogin do
  import Plug.Conn

  def init(opts), do: opts

  def call(conn, _opts) do
    if Doorman.logged_in?(conn) do
      conn
    else
      conn
      |> Phoenix.Controller.redirect(to: "/login")
      |> halt
    end
  end
end

doorman's People

Contributors

ashleyfoster avatar blakewilliams avatar drapergeek avatar gfontenot avatar lostkobrakai avatar orenyk avatar pdawczak avatar tomtaylor avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

doorman's Issues

Release soon?

Seems like a few things have changed without a release which lead to a bit of confusion about master vs release. Any chance you're looking to release soon?

Make `authenticate/2` more flexible

Currently, Doorman.authenticate/2 is hardcoded to find the user by an email field. This is the only thing in the library that depends on it, and it would nice to have that be more flexible.

For example, you could search for username:

Doorman.authenticate(password, username: username)

Or admins:

Doorman.authenticate(password, admin: true, email: email)

Doorman v0.6.0 at hex doesn't include Secret module

When using doorman v0.6.0 (current version at hex) I run into this compile error when trying to use put_session_secret from Doorman.Auth.Secret.

** (CompileError) lib/exgradebook/users/models/student.ex:5: module Doorman.Auth.Secret is not loaded and could not be found

When I use the master branch, all's good and the module is present.
Any chance for a release to hex that includes the module?

Testing authentication

Hi, I'm trying to write some basic acceptance tests for authentication. Signing in is fine, but I wasn't sure how to approach signing out. I'd like to log a user in during the setup function (i.e. conn |> login(user)), but using the built in Doorman.Login.Session.login/2 method fails with

** (ArgumentError) session not fetched, call fetch_session/2

I tried calling conn |> Plug.Conn.fetch_session |> login(user) but that failed with:

** (ArgumentError) cannot fetch session without a configured session plug

at which point I just used conn |> assign(:user_id, user.id), which did work, but makes me very uncomfortable. Do you have any suggestions about how to do this better or how to write test helpers for authentication? Thanks!

Application `:comeonin` is required

We just had a situation where our staging environment was denying that Comeonin was loaded at all, as follows:

** (exit) an exception was raised:
    ** (UndefinedFunctionError) function Comeonin.Bcrypt.hashpwsalt/1 is undefined (module Comeonin.Bcrypt is not available)
        Comeonin.Bcrypt.hashpwsalt("password123")
        (doorman) lib/auth/bcrypt.ex:45: Doorman.Auth.Bcrypt.hash_password/1
        (lightswap) lib/lightswap/users/users.ex:54: Lightswap.Users.create_user/1
        (lightswap) lib/lightswap_web/controllers/user_controller.ex:14: LightswapWeb.UserController.create/2
        (lightswap) lib/lightswap_web/controllers/user_controller.ex:1: LightswapWeb.UserController.action/2
        (lightswap) lib/lightswap_web/controllers/user_controller.ex:1: LightswapWeb.UserController.phoenix_controller_pipeline/2
        (lightswap) lib/lightswap_web/endpoint.ex:1: LightswapWeb.Endpoint.instrument/4
        (phoenix) lib/phoenix/router.ex:278: Phoenix.Router.__call__/1

After examining the build logs, we saw that we needed to explicitly include the application :comeonin in ours. See:

==> One or more direct or transitive dependencies are missing from
    :applications or :included_applications, they will not be included
    in the release:

    :comeonin

    This can cause your application to fail at runtime. If you are sure
    that this is not an issue, you may ignore this warning.

This was on Phoenix 1.3. Could that be the reason of this change? Also: why on staging (a virtual host running Ubuntu Xenial) but not on development (macOS Sierra)?

I'm not sure what the correct fix here is: whether to add the detail above to Doorman's documentation, to add this to its list of applications, or something else?

Prevent Timing Attacks

I'm currently reading Programming Phoenix and right there the authentication method is defined like this:

def login_by_username_and_pass(conn, username, given_pass, opts) do
  repo = Keyword.fetch!(opts, :repo)
  user = repo.get_by(Rumbl.User, username: username)

  cond do
    user && checkpw(given_pass, user.password_hash) ->
      {:ok, login(conn, user)}
    user ->
      {:error, :unauthorized, conn}
    true ->
      dummy_checkpw()
      {:error, :not_found, conn}
  end
end

Quite similar to doorman's, but it also uses Comeonin's dummy_checkpw function to prevent timing attacks. It would be great if you could add this as well :)

Case sensitivity?

I just realized that doorman is enforcing case-sensitivity on my user's email even though I'm using a lowercase unique index. I can solve this in my app but I'm wondering if a PR to doorman would be preferred so that by default we query lowercase?

If you're up for it, I can have a PR in today.

README typo

The "Logging in users" section contains a small typo. The code example correctly uses Doorman.Login.Session but the text above it references Doorman.Session.login/2 instead of Doorman.Login.Session.login/2 (unwieldy though it is).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.