Elixir Plugs
Tags: elixir, phoenix, plug
Published: 2022-12-03 06:33:21

---
layout: post
title: Elixir Plugs
published: true
excerpt: "I recently wrote an Elixir Plug for Soopr, a new tool I am building. This post talks about what plugs are and how you can write one yourself" 
tags: [Elixir, Phoenix, Plug]
comments: true
canonical_url: 'https://www.abhinav.co/elixir-plugs'
image: 'https://soopr.xyz/images/card?url=https://www.abhinav.co/elixir-plugs'

---

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](https://github.com/rack/rack) in Ruby. [Official documentation](https://hexdocs.pm/plug/readme.html) describes Plug as:
1. A specification for composable modules between web applications
2. 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](https://hexdocs.pm/plug/Plug.RequestId.html) - generates a unique request id for each request
* [Plug.Head](https://hexdocs.pm/plug/Plug.Head.html) - converts HEAD requests to GET requests
*  [Plug.Logger](https://hexdocs.pm/plug/Plug.Logger.html) - 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](https://hexdocs.pm/plug/Plug.BasicAuth.html) is one such plug, it provides Basic HTTP authentication.

<hr class="page-break">

## How are plugs chained?
In Phoenix world, plugs are generally added to your `endpoint.ex` or `router.ex` modules.

```elixir
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.

<hr class="page-break">

## 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. 

[Example](https://hexdocs.pm/phoenix/plug.html#function-plugs): 
```elixir
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: 
1. `init/1` where you can do the initialisation bit. It takes options as input, something that you can pass when using it in router or endpoint file.
2. `call/2` which is nothing but a function plug and takes exactly the same two parameters - `conn` and `options` 

```elixir
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:
1. `init/1` function takes options, however does nothing with it.
2. `call/2` function takes conn and options as input
3. Private function `get_brearer_auth_token/1` takes `conn` as input and tries finding `auth_token`. 
4. From `auth_token` we try finding an account. If we find the account, we add in our `conn` so that it is accessible to downstream plugs and controller functions.
5. In case we don’t find `auth_token` or `account` we respond with `401` and halt the request, `unauthorized/1` function takes care of that.

<hr class="page-break">

### Interesting Use cases
Here are a few interesting problems which you can possibly solve using your own custom plugs.
1. Firewall - block traffic from barred IP addresses or clients.
2. Last Seen - in messenger apps, last seen is maintained separately, using a plug you can update users' last seen value.
3. Throttle - throttle requests depending upon limits set by you or pricing plans.
4. Circuit Breaker - in case your downstream backend systems are facing trouble, you can decide to prevent traffic from creating more trouble.