Stuff 'n Things

Getting started with Guardian

• elixir and guardian

Updated: 2015-08-30 to support the 0.6.0 API.


For some reason, the first thing I look for when I’m building an application is authentication. For me things are interesting when there’s something going on on my apps, and that usually means letting people login.

I’ve been enjoying Elixir and so far there hasn’t been anything for authentication that suited what I wanted. Using Phoenix is so nice, and make things that are difficult or nearly impossible with other frameworks easy (looking at you Channels).

One of the things that I really like about Elixir/Eralng, is that making all elements of you application live in your application is (mostly) reasonable. Web requests, web-sockets, mail even raw TCP sockets are all on the table. Time for something new in auth.

Guardian is based on JWT. They are the self contained package of information that contains all the information you need for your authentication needs. For those unfamiliar I suggest having a look at the Registered Claim Names. These little packets of goodness can be used in a session, authorization header, passed in body params - they can even be used on raw sockets.

Lets look at what Guardian is not (for the moment).

Not to say that strategies won’t come sometime in the future, but for the moment I’m not convinced that they’re actually within scope of the library. Guardian strives to provide mechanisms for seamless authentication across different access patterns, devices and S2S communications. How you determine that a user is who they say they are is, at the moment beyond the scope.

So what’s it good for?

Guardian provides a mechanism for verifying previously asserted claims that someone is who they say they are. Do this for browser based, api, channel, socket communications or just because you can. The initial login is trivial, it’s the other parts of the system where things get interesting.

Since Guardian is based on JWT, you can share the tokens with other systems that you trust. The same shared secret will allow another system to verify the token which is great for S2S systems. Your Elixir application can mint the credentials (JWT) and other systems can verify them without a call to your Elixir application, or they can mint credentials and have them used by your Elixir app. Screw language differences.

Ok so lets see it then

I’m going to pull the code from my demo application: PhoenixGuardian.

Configuration

Add Guardian to your mix.deps:

defp deps do
  [
    # ...
    {:guardian, "~>0.6.0"},
    # ...
  ]
end

Guardian relies on Joken for it’s JWTs. Guardian will bring it in, but you’ll need to configure it.

config :joken, config_module: Guardian.JWT

config :guardian, Guardian,
      issuer: "MyApp",
      ttl: { 30, :days },
      verify_issuer: true,
      secret_key: "lksdjowiurowieurlkjsdlwwer",
      serializer: PhoenixGuardian.GuardianSerializer

A couple of things to note.

Guardian.Serializer

You’ll need the serializer so your app can serialize into and out of the token. Don’t worry, this is Elixir, they’re easy.

defmodule PhoenixGuardian.GuardianSerializer do
  @behaviour Guardian.Serializer

  alias PhoenixGuardian.Repo
  alias PhoenixGuardian.User

  def for_token(user = %User{}), do: { :ok, "User:#{user.id}" }
  def for_token(_), do: { :error, "Unknown resource type" }

  def from_token("User:" <> id), do: { :ok, Repo.get(User, String.to_integer(id)) }
  def from_token(_), do: { :error, "Unknown resource type" }
end

Need to support different model types? Just add a pattern match in your serializer!

So, at this point, we’re setup to use Guardian, we just need to decide where and how we’re going to apply it. I’ll show you the pieces.

Plug

Guardian comes with Plug integration. I’m going to focus on Phoenix but any plug will work.

There are three phases to Guardians plug integration.

  1. Verifying the token
  2. Loading the resource
  3. Requireing a verified token
pipeline :browser_session do
  plug Guardian.Plug.VerifySession # looks in the session for the token
  plug Guardian.Plug.LoadResource
end

pipeline :api do
  plug :accepts, ["json"]
  plug Guardian.Plug.VerifyHeader # Looks in the Authorization header for the token
  plug Guardian.Plug.LoadResource
end

These two pipelines will verify the token (if present) and load the resource if there was a verified token found. If the token isn’t there or is invalid, nothing bad happens, the load resource won’t do anything.

When we want to ensure that someone is authenticated we can ensure they have a verified token.

defmodule PhoenixGuardian.UserController do
  use PhoenixGuardian.Web, :controller

  alias PhoenixGuardian.User

  plug Guardian.Plug.EnsureAuthenticated, %{ on_failure: { PhoenixGuardian.SessionController, :new } } when not action in [:new, :create]

  # ....
end

Ok, so there’s some stuff going on there. Lets break it down.

The Guardian.Plug.EnsureAuthenticated checks to make sure there was a valid token found. If it finds one we move on.

If the plug cannot find a verified token for the connection, it calls the on_failure function. This function should be arity 2 and receive a Plug.Conn.t and it’s params. It’s up to this function to handle what should happen when things go south.

We could have put this plug in the pipeline, the only reason I didn’t do that for this controller was because I wanted more control over the actions it fires for, hence the when not action stuff.

Signing In.

Ok so, this is all good and well. We’ve configured it, setup a serializer, and created our pipelines, how to sign in?

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

  if changeset.valid? do
    user = Repo.insert(changeset)

    conn
    |> put_flash(:info, "User created successfully.")
    |> Guardian.Plug.sign_in(user, :token)
    |> redirect(to: user_path(conn, :index))
  else
    render(conn, "new.html", changeset: changeset)
  end
end

See that tiny line in the middle there. Guardian.Plug.sign_in(user, :csrf). That’s it. Once you do that, your token is generated, pumped into the connection and session and off you go.

The :csrf determines the tokens type (stored in the :aud field). This can be anything you want it to be (e.g. ‘token’, ‘csrf’, ‘api’, ‘oauth’ etc).

Logout

Guardian.Plug.logout(conn)
|> redirect_or_something

Channels

Ok so, I did say you could use this for channels right. Here it is:

some_html.html

<%= if Guardian.Plug.current_token(@conn) do %>
  <meta name='guardian_token' content="<%= Guardian.Plug.current_token(@conn) %>">
<% end %>

some_javascript.js

let socket = new Socket("/ws");
socket.connect();

let guardianToken = jQuery('meta[name="guardian_token"]').attr('content');

let chan = socket.chan("pings", { guardian_token: guardianToken });

phoenix_guardian/user_channel.ex

defmodule PhoenixGuardian.UsersChannel do
  use Phoenix.Channel
  use Guardian.Channel

  def join(_room, %{ claims: claims, resource: resource }, socket), do: { :ok, %{ message: "Joined" }, socket }
  def join(room, _, socket), do: { :error,  :authentication_required }

  def handle_in("ping", _payload, socket) do
    user = Guardian.Channel.current_resource(socket)
    broadcast socket, "pong", %{ message: "pong", from: user.email }
    { :noreply, socket }
  end

  def handle_guardian_auth_failure(reason), do: { :error, %{ error: reason } }
end

When Guardian finds a valid token, it extracts the claims and the resource, and calls join with them in the map. The keys to pattern match on for authenticated joins are claims and resource.

When Guardian cannot verify the token, it will call handle_guardian_auth_failure with the reason it failed.

Well, thats a whirlwind tour of Guardian as it stands today. There’s a lot more to say, but for the first post I think that will do.

comments powered by Disqus