README.md

# SigAuth

An HTTP API client authentication scheme based on RSA signatures.

In an HTTPS environment, the server's identity is trusted; proving the identity of the client can be accomplished in many ways; This scheme is inspired by the AWS authentication scheme, with a few modifications for simplicity's sake.

To prove identity, the client holds the private key of an RSA keypair, and creates a digital signature of the request as it is submitted.  The server can then validate the signature using the public key of the client claiming to submit the request.

This library makes no assumptions about your HTTP client or server, except that they allow you to specify and read the headers, body, method and path of a request.

## Example Use

### Generate Keys

The ssh-keygen command creates RSA public/private keypairs, saving the results in
files that SigAuth can load using `load_key/1`

The bash commands for generating new keys.  Note that SigAuth expects the
keyfiles to be unencrypted (i.e. no passphrase).

```
$ ssh-keygen -t rsa <enter>

Enter file in which to save the key: <type filename, enter>

Enter passphrase (empty for no passphrase): <enter>
Enter same passphrase again: <enter>
Your identification has been saved in <filename>.
Your public key has been saved in <filename>.pub.

```

### Client Request Signing

This client is using HTTPotion, but any client library that allows specifying
custom headers can be used (SigAuth provides headers as binary 2-tuples, e.g.:
`[{"authorization", "<authorization-token-stuff>"}, ...]`).

```elixir
priv_key = SigAuth.load_key("./test/testing_id_rsa")
nonce = System.system_time(:milliseconds)
username = "bob"
body = ""
path = "/api/users.27.json"
headers = SigAuth.sign("GET", path, nonce, body, username, priv_key)
# headers contains "authorization", and "x-sigauth-nonce" headers
HTTPotion.get("www.myapp.com" <> path, [headers: headers])
```

### Server Request Validation

SigAuth provides the `SigAuth.Plug` module to streamline request validation and
nonce maintenance.  Here is an example usage, with a public, non-authenticated
API endpoint followed by a private, authenticated endpoint.

```elixir
defmodule MyApp.ApiRouter
  use Plug.Router

  plug :match
  plug :dispatch

  # Not Authenticated:
  forward "/public", to: MyApp.PublicApiRouter

  plug SigAuth.Plug, credential_server: MyApp.CredServer # See below for details

  # Authenticated:
  forward "/private", to: MyApp.PrivateApiRouter
  # ...

```

## IMPORTANT NOTES

 - This plug *must* read the body of the request to verify the signature. This
 may well break your plug pipeline (Parsers, especially).  Currently, the body
 is stored in `conn.assigns[:body]` after it is read.  If you have an idea for
 a more elegant solution, feel free to provide a pull-request.

 - The username in the "authorization" header is stored for convenience in
 `conn.assigns[:username]`; this field can be used for user / role based
 authentication of individual endpoints; `SigAuth` has proven that the
 requestor possesses the private key associated with that username.

## CredentialServer

Rather than owning the enrollment and key management problem, SigAuth offloads
this work to you.  The `SigAuth.CredentialServer` module specifies the method
signatures that your credential server must expose for SigAuth.Plug to use it.

An example in-memory credential server is provided in
`SigAuth.ExampleCredentialServer`.

## Installation (Not yet)

```elixir
{:sig_auth, "~> 0.1.0"},
```

## Credits

I found the following links helpful in the construction of this application:

- [AWS authorization scheme](http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html)
- [Plug authentication example](http://luk3thomas.com/authentiation-in-elixir-plug-20160722.html)
- [Erlang :public_key cheatsheet](https://gist.github.com/zucaritask/3864572)