README.md

# ApiAuth

HMAC API authentication.

This is Elixir implementation should be compatible with [https://github.com/mgomes/api_auth](https://github.com/mgomes/api_auth)

## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `api_auth` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:api_auth, "~> 0.1.0"}
  ]
end
```

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at [https://hexdocs.pm/api_auth](https://hexdocs.pm/api_auth).

## Usage

### HTTPotion

To make a GET request:

```elixir
headers = ApiAuth.headers([], "/path", client_id, secret_key)

"http://example.com/path"
|> HTTPotion.get(headers: headers)
```

Or a POST request:

```elixir
body    = "post body"
headers = ApiAuth.headers([], "/post/path", client_id, secret_key,
                          method: "POST", content: body)

"http://example.com/post/path"
|> HTTPotion.post(body: body, headers: headers)
```

### HTTPoison

To make a GET request:

```elixir
headers = ApiAuth.headers([], "/path", client_id, secret_key)

"http://example.com/path"
|> HTTPoison.get(headers)
```

Or a POST request:

```elixir
body    = "{}"
headers = ApiAuth.headers(["Content-Type": "application/json"], "/post/path",
                           client_id, secret_key, method: "POST", content: body)

"http://example.com/path"
|> HTTPoison.post(body, headers)
```

### Phoenix

To authenticate all requests for a particular pipeline, create a new plug and configure it
to use `ApiAuth`:

```elixir
# lib/myapp_web/router.ex

defmodule Myapp.Router do
  use Myapp, :router

  pipeline :api do
    plug Myapp.Plugs.Authentication
    plug :accepts, ["json"]
  end

  scope "/", Myapp do
    ...
  end

  scope "/api", Myapp do
    pipe_through :api
    ...
  end
end
```

```elixir
# lib/myapp_web/plugs/authentication.ex

defmodule Myapp.Plugs.Authentication do
  import Plug.Conn

  def init(default), do: default

  def call(conn, _default) do
    client_id  = "client id"
    secret_key = "secret key"
    body       = get_body(conn)

    %{
      req_headers: req_headers,
      request_path: request_path,
      method: method,
    } = conn

    # you may need to add `content_algorithm: :md5` depending on the code signing the request
    # see the compatibility section of the README
    authentic = ApiAuth.authentic?(req_headers, request_path, client_id,
                                   secret_key, method: method,
                                   content: body)

    if authentic do
      conn
    else
      conn
      |> send_resp(:unauthorized, "")
      |> halt()
    end
  end

  defp get_body(conn) do
    case read_body(conn) do
      {:ok, body, _conn} -> body
      _                  -> ""
    end
  end
end
```

If you have multiple clients, you'll need to look up the secret key by client id.
The plug would look similar to the one above but with a few changes:

```elixir
defmodule Myapp.Plugs.Authentication do
  import Plug.Conn

  def call(conn, _default) do
    client_id = ApiAuth.client_id(conn.req_headers)
    {:ok, secret_key} = Myapp.Client.get_secret_key(client_id)

    ...
  end

  ...
end
```

### Compatibility

Using this library with [https://github.com/mgomes/api_auth](https://github.com/mgomes/api_auth) for Ruby/Rails
requires some configuration.

By default, the Rails library uses `sha1` as the HMAC hash function.
It also uses `md5` as the hash function for hashing content in PUT and POST requests.
This library uses `sha256` by default for both.

To make a request to a server which is using the Rails library with default configuration:

```elixir
headers
|> ApiAuth.headers(path, client_id, secret_key, content_algorithm: :md5,
                   signature_algorithm: :sha)
```

Or with `sha256` as the HMAC hash function:

```elixir
headers
|> ApiAuth.headers(path, client_id, secret_key, content_algorithm: :md5)
```

To tell if a request generated by the Rails library is authentic:

```elixir
headers
|> ApiAuth.authentic?(path, client_id, secret_key, content_algorithm: md5,
                      signature_algorithm: sha)
```

Or with `sha256` as the HMAC function:

```elixir
headers
|> ApiAuth.authentic?(path, client_id, secret_key, content_algorithm: md5)
```

## Running tests

* `mix deps.get`
* `mix test`