Skip to content

rzane/authenticator

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

37 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Authenticator Build Status

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:

  • plug :authenticate_session - Authenticate a user from the session.
  • plug :authenticate_header - Authenticate a user from the Authorization header.
  • plug :ensure_authenticated - Make sure a user is signed in.
  • plug :ensure_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:

def deps do [{:authenticator, "~> 1.0.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.

Here's an example implementation of an authenticator:

# lib/my_app_web/authentication.ex defmodule MyAppWeb.Authentication do use Authenticator, fallback: MyAppWeb.FallbackController 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, :unauthenticated} user -> {:ok, user} end end end

Session authentication

In your router, you'll define your plugs like so:

import MyAppWeb.Authenticator pipeline :browser do # snip... plug :authenticate_session end pipeline :authenticated do plug :ensure_authenticated end scope "/", MyAppWeb do pipe_through([:browser, :authenticated]) # declare protected routes here end

The controller where you're implementing login might look like this:

def create(conn, %{"email" => email, "password" => password}) do with {:ok, user} <- MyApp.Accounts.authenticate({email, password}) do conn |> MyAppWeb.Authentication.sign_in(user) |> redirect(to: "/") end end def destroy(conn, _params) do conn |> MyAppWeb.Authentication.sign_out() |> redirect(to: "/") end

API authentication

In your router, you'll define your plugs like so:

import MyAppWeb.Authentication pipeline :browser do # snip... plug :authenticate_header end pipeline :authenticated do plug :ensure_authenticated end scope "/", MyAppWeb do pipe_through([:browser, :authenticated]) # declare protected routes here end

The controller where you're implementing login might look like this:

def create(conn, %{"email" => email, "password" => password}) do with {:ok, user} <- MyApp.Accounts.authenticate({email, password}), {:ok, token} <- MyAppWeb.Authenticator.tokenize(user) do conn |> MyAppWeb.Authentication.sign_in(user, session: false) |> json(%{token: token}) end end def destroy(conn, _params) do conn |> MyAppWeb.Authentication.sign_out(session: false) |> send_resp(204, "") end

Fallback

When an error occurs, the call/2 function of your fallback will be called. This is where you'd handle errors.

See the Phoenix docs for an example fallback controller.

defmodule MyAppWeb.FallbackController do use Phoenix.Controller import MyAppWeb.Router.Helpers # This would mean that the `:ensure_authenticated` plug failed. def call(conn, {:error, :unauthenticated}) do case get_format(conn) do "html" -> conn |> put_flash(:error, "You need to sign in to continue.") |> redirect(to: login_path(conn)) |> halt() "json" -> conn |> put_status(401) |> json(%{error: "You need to sign in to continue."}) |> halt() end end # This would mean that the `:ensure_unauthenticated` plug failed. def call(conn, {:error, :already_authenticated}) do conn |> put_flash(:error, "You are already signed in.") |> redirect(to: page_path(conn, :index)) |> halt() end end

Usage with Authority

Authenticator works very nicely with Authority and Authority.Ecto.

Here's an example authenticator:

defmodule MyAppWeb.Authentication do use Authenticator, fallback: MyAppWeb.FallbackController @impl true def tokenize(user) do with {:ok, token} <- MyApp.Accounts.tokenize(user) do {:ok, token.token} end end @impl true def authenticate(token) do MyApp.Accounts.authenticate(%MyApp.Accounts.Token{token: token}) 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.

About

Provides the glue for authenticating HTTP requests.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages