Verify Shopify App Proxy in Elixir Phoenix with Plugs - Part 1: Expected Parameters

In future posts we’ll look at how we can check the timestamp to be within a reasonable timeframe and doing HMAC verification.

But the first thing we should do is create a method to check if the four parameters are set in the connection.

When Shopify receives an HTTP request for a proxied path, it will forward that request to the specified Proxy URL. The forwarded request adds the following parameters:

  1. shop: The domain for the shop.
  2. path_prefix: The proxy sub-path prefix at which the shop was accessed.
  3. timestamp: The time in seconds since midnight of January 1, 1970 UTC.
  4. signature: A hexadecimal encoded SHA-256 HMAC of the other parameters.

Given that the Proxy URL is set to, the client’s IP address is and the applications shared secret is hush, the forwarded request will look like the following:

GET /proxy/extra/path/components?extra=1&extra=2&

— Paraphrased from Shopify Developer Resources

Our first Plug is called Paramv, using Paramv we can pattern match the call with a Plug.Conn tuple that contains a params key and inside that, each of the keys we’re expecting.

We then create an additional function that matches everything, in that function we call Cogn.Plug.Errors.failed_connection/2 which checks to see what environment we’re in. If we’re running in :dev, we send a warning via Logger and proceed, else we send a 401 halt the connection and raise(Cogn.BadRequestError).

defmodule Cogn.Plug.Paramv do
  def init(default), do: default

  @doc """
  Check that all the parameters we need are set in the connection.
  def call(
          params: %{
            "shop" => _,
            "path_prefix" => _,
            "timestamp" => _,
            "signature" => _
        } = conn,
      ) do

  @doc """
  If the parameters are not set we'll render an error and halt the pipeline
  def call(conn, _default) do
    Cogn.Plug.Errors.failed_connection(conn, :Paramv)

And now adding in our Error Handler.

Remember: Replace GenieWeb with the name of your application.

defmodule Cogn.Plug.Errors do
  require Logger

  @doc """
  Calls `failed_connection/3` with `Mix.env()` as the last parameter.
  def failed_connection(conn, who) do
    failed_connection(conn, who, Mix.env())

  defp failed_connection(conn, who, :dev) do
    Logger.warn(fn ->
      "#{who}: " <>
        "Accessing proxy route with bad params. " <>
        "Doing this from a production enviroment will fail with the error code 400."


  defp failed_connection(conn, _who, _) do
    Logger.error(fn ->
      "Accessing proxy route with bad params. #{conn.params}"

    |> Plug.Conn.put_status(401)
    |> Phoenix.Controller.render(GenieWeb.ErrorView, :"401")
    |> Plug.Conn.halt()