PhoenixApiToolkit.Security.HmacPlug (Phoenix API Toolkit v3.0.0) View Source

Checks HMAC authentication. Expects a HMAC-<some_algorithm> of the request body to be present in the "authorization" header. Supported algorithms are those supported by :crypto.mac/4. Relies on PhoenixApiToolkit.CacheBodyReader being called by Plug.Parsers.

To be considered a valid request by the plug, a request has to meet the following criteria:

  • the request path must match the "path" stated in the request body
  • the request HTTP method must match the "method" stated in the request body
  • the request "timestamp" must not be older than max_age

If the token is invalid for any reason, PhoenixApiToolkit.Security.HmacVerificationError is raised, resulting in a 401 Unauthorized response.

Configuration options

  • hmac_secret as a binary (mandatory): the secret used to create the HMAC
  • hash_algorithm as an atom (optional, defaults to :sha256) the hashing algorithm used to create the HMAC
  • max_age in seconds (optional, defaults to 120) the maximum age of the request before it is considered invalid

Request body format example

{
  "path": "/api/v2/accounts",
  "method": "POST",
  "timestamp": 1321321545,
  "contents": {
    "key1": "value1",
    "something": "else"
  }
}

Examples

use Plug.Test

alias PhoenixApiToolkit.Security.HmacPlug
import PhoenixApiToolkit.TestHelpers
import PhoenixApiToolkit.CacheBodyReader

@secret "supersecretkey"

def opts, do: HmacPlug.init(lazy_hmac_secret: fn -> @secret end)

def conn_for_hmac(method, path, raw_body) do
  conn(method, path, raw_body |> Jason.decode!())
  |> application_json()
  |> put_raw_body(raw_body)
end

# a correctly signed request is passed through
iex> body = create_hmac_plug_body("/", "POST", %{hello: "world"})
iex> {:ok, _raw_body, conn} = conn_for_hmac(:post, "/", body) |> put_hmac(body, @secret) |> cache_and_read_body()
iex> conn = HmacPlug.call(conn, opts())
iex> conn.body_params["contents"]
%{"hello" => "world"}

# requests that are noncompliant result in a PhoenixApiToolkit.Security.HmacVerificationError
iex> body = create_hmac_plug_body("/", "PUT", %{hello: "world"})
iex> {:ok, _raw_body, conn} = conn_for_hmac(:post, "/", body) |> put_hmac(body, @secret) |> cache_and_read_body()
iex> HmacPlug.call(conn, opts())
** (PhoenixApiToolkit.Security.HmacVerificationError) HMAC invalid: method mismatch

iex> body = create_hmac_plug_body("/", "POST", %{hello: "world"}, 12345)
iex> {:ok, _raw_body, conn} = conn_for_hmac(:post, "/", body) |> put_hmac(body, @secret) |> cache_and_read_body()
iex> HmacPlug.call(conn, opts())
** (PhoenixApiToolkit.Security.HmacVerificationError) HMAC invalid: expired