Packages

A Plug adapter for HMAC authentication

Current section

Files

Jump to
plug_hmac_auth lib plug_hmac_auth.ex
Raw

lib/plug_hmac_auth.ex

defmodule PlugHmacAuth do
@moduledoc """
`PlugHmacAuth` provides a `Plug` for `HMAC authentication`.
"""
@behaviour Plug
import Plug.Conn
@type opts :: [
key_access_id: String.t(),
key_signature: String.t(),
hmac_hash_algo:
:md4
| :md5
| :sha
| :sha224
| :sha256
| :sha384
| :sha3_224
| :sha3_256
| :sha3_384
| :sha3_512
| :sha512,
secret_handler: module(),
error_handler: module()
]
@spec init(opts :: Keyword.t()) :: Keyword.t()
def init(opts), do: opts
@spec call(Plug.Conn.t(), Keyword.t()) :: Plug.Conn.t()
def call(conn, opts) do
key_access_id = Keyword.get(opts, :key_access_id, "no-key-access-id")
key_signature = Keyword.get(opts, :key_signature, "no-key-signature")
hmac_hash_algo = Keyword.get(opts, :hmac_hash_algo, :no_hash_algo)
secret_handler = Keyword.get(opts, :secret_handler, :no_secret_handler)
error_handler = Keyword.get(opts, :error_handler, :no_error_handler)
with {:ok, access_key} <- fetch_token_from_header(conn, key_access_id),
{:ok, access_signature} <- fetch_token_from_header(conn, key_signature),
{:ok, secret_key} <- secret_handler.get_secret_key(access_key),
:ok <- verify_payload(conn, secret_key, access_signature, hmac_hash_algo) do
conn
else
{:error, code} ->
conn |> error_handler.auth_error(code) |> halt()
end
end
@spec verify_payload(
Plug.Conn.t(),
String.t(),
String.t(),
:md4
| :md5
| :sha
| :sha224
| :sha256
| :sha384
| :sha3_224
| :sha3_256
| :sha3_384
| :sha3_512
| :sha512
) :: :ok | {:error, :invalid_signature}
def verify_payload(conn, secret_key, access_signature, hmac_hash_algo)
def verify_payload(conn, secret_key, access_signature, hmac_hash_algo) do
if conn |> get_payload() |> gen_signature(secret_key, hmac_hash_algo) == access_signature do
:ok
else
{:error, :invalid_signature}
end
end
@spec get_payload(Plug.Conn.t()) :: String.t()
def get_payload(%Plug.Conn{method: "GET", query_string: query_string}), do: query_string
def get_payload(%Plug.Conn{assigns: %{raw_body: raw_body}}), do: raw_body
@doc """
Returns signatre generated by payload and secret key.
"""
@spec gen_signature(String.t(), String.t(), atom()) :: String.t()
def gen_signature(payload, secret_key, hmac_hash_algo),
do: :crypto.hmac(hmac_hash_algo, secret_key, payload) |> Base.encode64()
@doc """
Returns the first token from http request header by specific key.
"""
@spec fetch_token_from_header(Plug.Conn.t(), binary()) ::
{:error, :invalid_key} | {:ok, binary()}
def fetch_token_from_header(conn, key)
def fetch_token_from_header(conn, key) when is_binary(key) do
case get_req_header(conn, key) do
[] -> {:error, :invalid_key}
[token | _] -> {:ok, String.trim(token)}
end
end
def fetch_token_from_header(_conn, key) when not is_binary(key) do
{:error, :invalid_key}
end
end