README.md

# Authenticator [![Build Status](https://travis-ci.org/rzane/authenticator.svg?branch=master)](https://travis-ci.org/rzane/authenticator)

This module provides the glue for authenticating HTTP requests.

By using `Authenticator`, you'll get the following functions:

* `sign_in(conn, user)` - Sign a user in.
* `sign_out(conn)` - Sign a user out.
* `signed_in?(conn)` - Check if a user is signed in.

You'll also get the following plugs:

* `Authenticator.Session` - Authenticate a user from the session.
* `Authenticator.Header` - Authenticate a user from the `Authorization` header.
* `Authenticator.Authenticated` - Make sure a user is signed in.
* `Authenticator.Unauthenticated` - Make sure a user is *not* signed in.

## Installation

The package can be installed by adding `authenticator` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [{:authenticator, "~> 0.1.0"}]
end
```

## Usage

To use `Authenticator`, you'll need to define the following functions:

* `tokenize(resource)` - Serialize the user into a "token" that can be stored in the session.
* `authenticate(resource)` - Given a "token", locate the user.
* `fallback(conn, reason)` - Handle authentication errors.

Here's an example implementation of an authenticator:

```elixir
# lib/my_app_web/authenticator.ex

defmodule MyAppWeb.Authenticator do
  use Authenticator

  import Phoenix.Controller
  import MyAppWeb.Router.Helpers

  alias MyApp.Repo
  alias MyApp.Accounts.User

  @impl true
  def tokenize(user) do
    {:ok, to_string(user.id)}
  end

  @impl true
  def authenticate(user_id) do
    case Repo.get(User, user_id) do
      nil ->
        {:error, :not_found}

      user ->
        {:ok, user}
    end
  end

  @impl true
  def fallback(conn, :not_found) do
    conn |> redirect(to: login_path(conn)) |> halt()
  end

  def fallback(conn, :not_authenticated) do
    conn
    |> put_flash(:error, "You need to sign in to continue.")
    |> redirect(to: login_path(conn))
    |> halt()
  end

  def fallback(conn, :not_unauthenticated) do
    conn
    |> put_flash(:error, "You are already signed in.")
    |> redirect(to: root_path(conn))
    |> halt()
  end
end
```

## Plugs

All of the plugs provided by `Authenticator` expect your app's authenticator as an argument.

```elixir
pipeline :browser do
  # snip...

  plug Authenticator.Session, with: MyAppWeb.Authenticator
end

pipeline :authenticated do
  plug Authenticator.Authenticated, with: MyAppWeb.Authenticator
end

scope "/", MyAppWeb do
  pipe_through([:browser, :authenticated])

  # declare protected routes here
end
```

## Usage with Authority

`Authenticator` supports [`Authority`](https://github.com/infinitered/authority) and [`Authority.Ecto`](https://github.com/infinitered/authority_ecto) out of the box.

Here's an example authenticator that takes advantage of `Autenticator.Authority`:

```elixir
defmodule MyAppWeb.Authenticator do
  use Authenticator
  use Authenticator.Authority,
    token_schema: Accounts.Token,
    tokenization: Accounts,
    authentication: Accounts

  @impl true
  def fallback(conn, _reason) do
    conn
    |> Plug.Conn.redirect(to: "/login")
    |> Plug.Conn.halt()
  end
end
```

> *Note:* In the above example, we're serializing the user into a token. If you're using `Authority.Ecto`, tokens are stored in the database. The benefit of using a token (as opposed to the user's ID), is that we can revoke specific sessions by deleting tokens from the database.