Of late, I have been writing some Elixir code to build an API layer for Soopr, a SaaS tool I am working on. This system needed a simplistic authentication mechanism and I ended up writing a custom Plug for it. This post explains what are plugs and how you can write one yourself.
What is a Plug?
In Elixir world, Plug is a bit similar to Rack in Ruby. Official documentation describes Plug as:
- A specification for composable modules between web applications
 - An abstraction layer for connection adapters for different web servers in the Erlang VM
 
Plugs can be chained together and they can be very powerful as well as can add simplicity and readability to your code.
If I were to describe Plug, plugs are a mechanism through which either you can transform your request/response or you can decide to filter requests that reach to your controller.
In Plug/Phoenix world, %Plug.Conn{} in the connection struct that contains both request & response paramters and is typically called conn.
Transformation
It essentially means modifying conn (technically, creating a copy of original conn) and adding more details to it.  These plugs also need to return modified conn struct so that plugs can be chained together. Some examples of plugs are:
- Plug.RequestId - generates a unique request id for each request
 - Plug.Head - converts HEAD requests to GET requests
 - Plug.Logger - logs requests
 
Filter
There are cases when you want to stop HTTP requests if they don’t meet your criteria. Example - they come from a blocked IP or more common example - don’t have required authentication details. In these cases you use such plugs. Plug.BasicAuth is one such plug, it provides Basic HTTP authentication.
How are plugs chained?
In Phoenix world, plugs are generally added to your endpoint.ex or router.ex modules.
defmodule MyAppWeb.Router do
  use MyAppWeb, :router
  import Plug.BasicAuth
  pipeline :api do
    plug :accepts, ["json"] 
    plug :basic_auth,
      username: System.fetch_env!("AUTH_USER"),
      password: System.fetch_env!("AUTH_KEY")
  end
  scope "/api/v1/", MyAppWeb do
    pipe_through :api
    get "/posts", PostController, :index
  end
end
In this example - our api pipeline would accept only json requests and require basic authentication. This api pipeline is then used to define a GET /api/v1/posts route.
How to build your own Plug?
There are two ways in which you can write your own Plug - Function or Module.
1. Function Plugs
Function plugs are generally used if your plug is simple enough and doesn’t have any costly initialisation requirement. To write a function plug, simply define a function which takes two inputs -  conn and options. 
def introspect(conn, _options) do
  IO.puts """
  Verb: #{inspect(conn.method)}
  Host: #{inspect(conn.host)}
  Headers: #{inspect(conn.req_headers)}
  """
  conn
end
2. Module Plugs
Module plugs are useful when you have a bit heavy initialisation process or you need auxilary functions to keep your code readable. For a Module Plug, you need you to define following two functions inside an elixir module:
- 
init/1where you can do the initialisation bit. It takes options as input, something that you can pass when using it in router or endpoint file. - 
call/2which is nothing but a function plug and takes exactly the same two parameters -connandoptions 
defmodule MyAppWeb.BearerAuth do
  import Plug.Conn
  alias MyApp.Account
  def init(options) do
    options
  end
  def call(conn, _options) do
    case get_bearer_auth_token(conn) do
      nil ->
        conn |> unauthorized()
      :error ->
        conn |> unauthorized()
      auth_token ->
        account =
          Account.get_from_token(auth_token)
        if account do
          assign(conn, :current_account, account)
        else
          conn |> unauthorized()
        end
    end
  end
  defp get_bearer_auth_token(conn) do
    with ["Bearer " <> auth_token] <- get_req_header(conn, "authorization") do
      auth_token
    else
      _ -> :error
    end
  end
  defp unauthorized(conn) do
    conn
    |> resp(401, "Unauthorized")
    |> halt()
  end
end
This is taken from an actual plug which I wrote for Soopr (I have just changed the names). Though I didn’t have any heavy initialisation requirements, I decided to use module way of writing so that I can define a couple of private helper functions. In this example:
- 
init/1function takes options, however does nothing with it. - 
call/2function takes conn and options as input - Private function 
get_brearer_auth_token/1takesconnas input and tries findingauth_token. - From 
auth_tokenwe try finding an account. If we find the account, we add in ourconnso that it is accessible to downstream plugs and controller functions. - In case we don’t find 
auth_tokenoraccountwe respond with401and halt the request,unauthorized/1function takes care of that. 
Interesting Use cases
Here are a few interesting problems which you can possibly solve using your own custom plugs.
- Firewall - block traffic from barred IP addresses or clients.
 - Last Seen - in messenger apps, last seen is maintained separately, using a plug you can update users' last seen value.
 - Throttle - throttle requests depending upon limits set by you or pricing plans.
 - Circuit Breaker - in case your downstream backend systems are facing trouble, you can decide to prevent traffic from creating more trouble.
 
    
Top comments (0)