README.md

# APIacAuthMTLS

An `APIac.Authenticator` plug implementing **section 2** of
OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens
([RFC8705](https://tools.ietf.org/html/rfc8705))

Using this scheme, authentication is performed thanks to 2 elements:
- TLS client certificate authentication
- the `client_id` parameter of the `application/x-www-form-urlencoded` body

TLS client certificate authentication may be performed thanks to two methods:
- authentication with a certificate issued by a Certificate Authority (CA) which is called
[PKI Mutual-TLS Method](https://tools.ietf.org/html/rfc8705#section-2.1).
In this case, one of the following certificate attribute is checked against
this attribute registered for the `client_id`:
  - Distinguished name
  - SAN DNS
  - SAN URI
  - SAN IP address
  - SAN email
- authentication with a self-signed, self-issued certificate which is called
[Self-Signed Certificate Mutual-TLS Method](https://tools.ietf.org/html/rfc8705#section-2.2).
In this case, the certificate is checked against the **subject public key info**
of the registered certificates of the `client_id`

## Installation

```elixir
def deps do
  [
    {:apiac_auth_mtls, "~> 1.0"},
  ]
end
```

## Plug options

- `:allowed_methods`: one of `:pki`, `:selfsigned` or `:both`. No default value,
mandatory option
- `:pki_callback`: a
`(String.t -> String.t | {tls_client_auth_subject_value(), String.t()} | nil)`
function that takes the `client_id` as a parameter and returns its DN as a `String.t()` or
`{tls_client_auth_subject_value(), String.t()}` or `nil` if no DN is registered for
that client. When no `t:tls_client_auth_subject_value/0` is specified, defaults to
`:tls_client_auth_subject_dn`
- `:selfsigned_callback`: a `(String.t -> binary() | [binary()] | nil)`
function that takes the `client_id` as a parameter and returns the certificate
or the list of the certificate for `the client_id`, or `nil` if no certificate
is registered for that client. Certificates can be returned in DER-encoded format, or
native OTP certificate structure
- `:cert_data_origin`: origin of the peer cert data. Can be set to:
  - `:native`: the peer certificate data is retrieved from the connection. Only works when
  this plug is used at the TLS termination endpoint. This is the *default value*
  - `{:header_param, "Header-Name"}`: the peer certificate data, and more specifically the
  parameter upon which the decision is to be made, is retrieved from an HTTP header. When
  using this feature, **make sure** that this header is filtered by a n upstream system
  (reverse-proxy...) so that malicious users cannot inject the value themselves. For instance,
  the configuration could be set to: `{:header_param, "SSL_CLIENT_DN"}`. If there are several
  values for the parameter (for instance several `dNSName`), they must be sent in
  separate headers. Not compatible with self-signed certiticate authentication
  - `:header_cert`: the whole certificate is forwarded in the `"Client-Cert"` HTTP header
  as a Base64 encoded value of the certificate's DER serialization, in conformance with
  [Client-Cert HTTP Header: Conveying Client Certificate Information from TLS Terminating Reverse Proxies to Origin Server Applications (draft-bdc-something-something-certificate-01)](https://tools.ietf.org/html/draft-bdc-something-something-certificate-01)
  - `{:header_cert, "Header-Name"}`: the whole certificate is forwarded in the
  "Header-Name" HTTP header as a Base64 encoded value of the certificate's DER serialization
  - `{:header_cert_pem, "Header-Name"}`: the whole certificate is forwarded in the
  "Header-Name" as a PEM-encoded string and retrieved by this plug
- `:set_error_response`: function called when authentication failed. Defaults to
`APIacAuthBasic.send_error_response/3`
- `:error_response_verbosity`: one of `:debug`, `:normal` or `:minimal`.
Defaults to `:normal`

## Example

```elixir
plug APIacAuthBasic, allowed_methods: :both,
                      selfsigned_callback: &selfsigned_certs/1,
                      pki_callback: &get_dn/1

# further

defp selfsigned_certs(client_id) do
  :ets.lookup_element(:clients, :client_id, 5)
end

defp get_dn("client-1") do
  "/C=US/ST=ARI/L=Chicago/O=Agora/CN=API access certificate"
end

defp get_dn(_), do: nil
```

## Configuring TLS for client authentication

See the module's information for further information, examples, and the security considerations.