Simple token authentication for Phoenix API
Tags: elixir, phoenix, authentication, api
Published: 2021-04-03 03:48:57

Recently I am really into IoT development using [Elixir programming language](https://elixir-lang.org/), [Nerves IoT platform](https://www.nerves-project.org/) and [Phoenix web framework](https://phoenixframework.org/). After quickly learning the basics of electronics, I built a real-time temperature and humidity monitoring system for my living room. It has been successful and really fun.

Now that the system is working well, I want to make it more secure. Today I will implement simple token authentication for my API server.

[日本語版](https://qiita.com/mnishiguchi/items/2a447f9138c5b61e1147)

[![hello-nerves-2](https://user-images.githubusercontent.com/7563926/113411295-6508b380-9383-11eb-81ef-942e0999d0cd.gif)](https://dev.to/mnishiguchi/iot-development-using-rapberry-pi-and-elixir-iij)

## Plans

- Implement custom plugs for token authentication
- Manually generates a token for each user in IEx
- Reject access if a token in the request headers is missing or invalid

## Phoenix.Token

Thankfully, Phoenix has all the useful utilities for generating and verifying a token in [`Phoenix.Token`](https://hexdocs.pm/phoenix/Phoenix.Token.html) module. Nice!

## Custom plug module example

Using `Phoenix.Token` module, I wrote two custom plugs:

- `ExampleWeb.API.Auth` - a module plug that verifies the bearer token in the request headers and assigns `:current_user`
- `ExampleWeb.API.Auth.authenticate_api_user/2` - a function plug that ensures that `:current_user` value is present.

I learned about the plugs from the [Programming Phoenix](https://pragprog.com/titles/phoenix14/programming-phoenix-1-4/) book.

```elixir
defmodule ExampleWeb.API.Auth do
  @moduledoc """
  A module plug that verifies the bearer token in the request headers and
  assigns `:current_user`. The authorization header value may look like
  `Bearer xxxxxxx`.
  """

  import Plug.Conn
  import Phoenix.Controller

  def init(opts), do: opts

  def call(conn, _opts) do
    conn
    |> get_token()
    |> verify_token()
    |> case do
      {:ok, user_id} -> assign(conn, :current_user, user_id)
      _unauthorized -> assign(conn, :current_user, nil)
    end
  end

  @doc """
  A function plug that ensures that `:current_user` value is present.

  ## Examples

      # in a router pipeline
      pipe_through [:api, :authenticate_api_user]

      # in a controller
      plug :authenticate_api_user when action in [:index, :create]

  """
  def authenticate_api_user(conn, _opts) do
    if Map.get(conn.assigns, :current_user) do
      conn
    else
      conn
      |> put_status(:unauthorized)
      |> put_view(ExampleWeb.ErrorView)
      |> render(:"401")
      # Stop any downstream transformations.
      |> halt()
    end
  end

  @doc """
  Generate a new token for a user id.

  ## Examples

      iex> ExampleWeb.API.Auth.generate_token(123)
      "xxxxxxx"

  """
  def generate_token(user_id) do
    Phoenix.Token.sign(
      ExampleWeb.Endpoint,
      inspect(__MODULE__),
      user_id
    )
  end

  @doc """
  Verify a user token.

  ## Examples

      iex> ExampleWeb.API.Auth.verify_token("good-token")
      {:ok, 1}

      iex> ExampleWeb.API.Auth.verify_token("bad-token")
      {:error, :invalid}

      iex> ExampleWeb.API.Auth.verify_token("old-token")
      {:error, :expired}

      iex> ExampleWeb.API.Auth.verify_token(nil)
      {:error, :missing}

  """
  @spec verify_token(nil | binary) :: {:error, :expired | :invalid | :missing} | {:ok, any}
  def verify_token(token) do
    one_month = 30 * 24 * 60 * 60

    Phoenix.Token.verify(
      ExampleWeb.Endpoint,
      inspect(__MODULE__),
      token,
      max_age: one_month
    )
  end

  @spec get_token(Plug.Conn.t()) :: nil | binary
  def get_token(conn) do
    case get_req_header(conn, "authorization") do
      ["Bearer " <> token] -> token
      _ -> nil
    end
  end
end
```

## How to use custom plugs

The function plug `authenticate_api_user/2` needs to be `import`ed before use. There are two possible scenarios.

### A: when used in a router pipeline

This pattern is useful to affect all the controllers in the pipeline. We need to import the function plug within the `quote` block of `ExampleWeb.router` function.

```diff
 defmodule ExampleWeb do

   ...

   def router do
     quote do
       use Phoenix.Router

       import Plug.Conn
       import Phoenix.Controller
       import Phoenix.LiveView.Router
+      import ExampleWeb.API.Auth, only: [authenticate_api_user: 2]
     end
   end
```

Here is an example usage.

```diff
 defmodule ExampleWeb.Router do
   use ExampleWeb, :router

   pipeline :api do
     plug :accepts, ["json"]
+    plug ExampleWeb.API.Auth
   end

   scope "/api", ExampleWeb do
-    pipe_through [:api]
+    pipe_through [:api, :authenticate_api_user]

     resources "/measurements", API.Environment.MeasurementController, only: [:index,  :show, :create]
   end
```

### B: when used in a specific controller

This pattern is useful when we want to affect only specific controller actions. We need to import the function plug within the `quote` block of `ExampleWeb.controller` function.

```diff
 defmodule ExampleWeb do

   ...

   def controller do
     quote do
       use Phoenix.Controller, namespace: ExampleWeb

       import Plug.Conn
       import ExampleWeb.Gettext
+      import ExampleWeb.API.Auth, only: [authenticate_api_user: 2]
       alias ExampleWeb.Router.Helpers, as: Routes
     end
   end
```

Here is an example usage.

```diff
 defmodule ExampleWeb.Router do
   use ExampleWeb, :router

   pipeline :api do
     plug :accepts, ["json"]
+    plug ExampleWeb.API.Auth
   end
```

```diff
 defmodule ExampleWeb.API.MeasurementController do
   use ExampleWeb, :controller

   alias Example.Measurement

   action_fallback ExampleWeb.API.FallbackController
+
+  plug :authenticate_api_user when action in [:create]
+
```

## Quick test

In an IEx console, generate a token.

```elixir
iex> ExampleWeb.API.Auth.generate_token(1)
"xxxxxxx"
```

Then hit an API endpoint with or without a token.

```
❯ curl -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer SFMyNTY.g2gDYQFuBgCtL76NeAFiAAFRgB" \
  -d '{"measurement": {"temperature_c": "23.5"}}' \
  http://localhost:4000/api/measurements
{"data":{"id":37,"temperature_c":23.5}}

❯ curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"measurement": {"temperature_c": "23.5" }}' \
  http://localhost:4000/api/measurements
"Unauthorized"
```

## Production

According to the [documentation](https://hexdocs.pm/phoenix/Phoenix.Token.html) [`SECRET_KEY_BASE`](https://hexdocs.pm/phoenix/deployment.html#handling-of-your-application-secrets) is used for the token generation, so the token generated in development environment won't work in production. I generate a token in the production IEx.

That's it!