Bouncer (alpha, not used in production yet)
Token-based authorization and session management for Phoenix (Elixir)
Why
I needed a way to authorize API requests to my Phoenix application. Addict didn't fit the bill since it uses Phoenix's built-in session system. Phoenix uses cookies to authorize requests but when dealing with an API, it's easier to deal with an Authorization header. Phoenix's session system also uses memory or ETS to store session data and this wouldn't work for my application which would be scaled horizontally and so would be running on multiple machines. Redis is great at solving this problem because it's crazy-fast and can be accessed by multiple machines. The ecosystem around Redis is strong so working with the session data is pretty easy.
Guardian also wouldn't work because it uses JSON Web Tokens (JWT) as the basis for it's authorization scheme. JWT can work but from my understanding, you need a system to refresh the tokens in case your user's JWT gets into the wrong hands. Guardian doesn't yet provide this functionality and I personally think the whole concept is more flawed than the traditional session-based system which allows you to immediately invalidate sessions.
Installation
Bouncer is available in Hex, the package can be installed as:
-
Add bouncer to your list of dependencies in
mix.exs:
```elixir
def deps do
[{:bouncer, "~> 0.0.6"}]
end
```- Ensure bouncer is started before your application:
```elixir
def application do
[applications: [:bouncer]]
end
```Requirements & Configuration
Bouncer only has one session store adapter so far: Redis. Here, I'm using Redix and the example in it's docs to set up a connection pool to Redis. I then add the RedixPool module (which is a Supervisor) to my applications supervisor tree so that it connects when the application starts up. Add the following configuration once you have this set up:
config :bouncer,
adapter: Bouncer.Adapters.Redis,
redis: MyApp.RedixPoolBouncer also requires the Phoenix framework because it uses it's Token module to generate tokens that are used both as an Authorization header and a session key. Bouncer provides a Plug that can be used to authorize a request for certain controllers and/or controller actions:
# This would be added near the top of a UserController for example
plug Bouncer.Plugs.Authorize when action in [:show, :update, :delete]Documentation
The source is really small so reading through it should be straight-forward but the full package documentation is available at https://hexdocs.pm/bouncer.
Example of a SessionController
Here's and example of how you can use the Session API in your application:
# web/controllers/session_controller.ex
defmodule MyApp.SessionController do
use MyApp.Web, :controller
alias MyApp.User
alias Bouncer.Session
plug :scrub_params, "user" when action in [:create]
plug Bouncer.Plugs.Authorize when action in [:delete]
def create(conn, %{"user" => user_params}) do
case user = Repo.get_by(User, %{username: user_params["username"]}) do
nil ->
Bcrypt.dummy_checkpw()
send_resp(conn, :bad_request, "")
user ->
if Comeonin.checkpw(user_params.password, user.encrypted_password) do
user_map = User.to_map(user, true)
{_, token} = Session.create(conn, user_map)
render(UserView, "create.json", user: user_map, token: token)
else
send_resp(conn, :bad_request, "")
end
end
end
def delete(conn) do
case Session.destroy(conn.assigns.token) do
{ :ok, _ } -> send_resp(conn, :no_content, "")
{ status, response } -> send_resp(conn, :bad_request, "")
end
end
end