Plug for verifying request signatures according to the IETF HTTP signatures draft specification.
Supports the following algorithms:
- “hs2019”, using ECDSA, RSASSA-PSS or HMAC
- “rsa-sha256”, using RSASSA-PKCS1-v1_5
- “rsa-sha1”, using RSASSA-PKCS1-v1_5
Signing the request body requires use of the HTTP Digest header, which can be added through the following package:
A very simple sample app using both plugs is available in the PlugSignature GitHub repo. This code also includes two client implementations (one in Elixir using Tesla, and one as a shell script using OpenSSL/cURL) for testing.
Great work! I’m very curious about the use-case it solves, if it’s no secret. I remember having to choose between such a scheme and signed JWT (which transforms the JSON payload to a base64 payload, is heavier, etc.). We finally chose not using this HTTP signature mainly because of a host of proxies that would have inevitably modified some signed headers and lack of implementation.
Also, I see you support RSA with PSS padding. So far I thought it wasn’t supported by Erlang. That’s good news! Thanks for the code and comment that goes with it, I’ll you use it soon
From the IETF draft:
When communicating over the Internet using the HTTP protocol, it can
be desirable for a server or client to authenticate the sender of a
particular message. It can also be desirable to ensure that the
message was not tampered with during transit. This document
describes a way for servers and clients to simultaneously add
authentication and message integrity to HTTP messages by using a
It looks like this is similar to how companies like Shopify and GitHub allow you to verify the content of a payload by signing the body of the HTTP request with a private/secret key. I’ve seen this used mostly with webhooks. Apologies if that’s incorrect.
Looks awesome though!
Leaving aside projects where the use of this standard is prescribed, e.g. due to integration or compliance requirements…
Compared to other authentication mechanisms that work at the HTTP layer, signatures offer a number of benefits:
- Credentials are not transmitted on the wire, unlike Basic or Bearer auth; this is generally a good thing regardless of whether the channel is encrypted
- It does not require a challenge/response roundtrip and keeping state on the server, unlike Digest auth
- It verifies the integrity of selected parts of the HTTP request, preventing modification of those parts by intermediaries
- Support for non-repudiation (except when using HMAC)
- Private key never leaves the client, so it is less likely to leak compared to symmetrical credentials that need to be defined in client and server (again, does not apply to HMAC)
Compared to authentication at the TLS layer, e.g. a client certificate:
- Does not impose requirements on TLS proxies (e.g. cloud load balancers)
- Works with browser-based clients
Of course you could implement the whole thing in a higher layer, using HTTP only as a dumb pipe. I generally try to avoid pushing functionality up the stack, preferring to leverage the services of lower layers (in this case HTTP) and handling only my business logic in the application layer. This allows me to delegate things like authentication and authorisation (e.g. to Plugs or API gateways) without impacting my APIs.
As for headers being manipulated between client and server, this hasn’t been an issue for me: headers that are likely to be modified often do not require integrity protection. In earlier versions of the spec the Date header was included in the signature, to limit the signature validity, and this could be problematic (also for AJAX clients), but the latest drafts use the created/expires parameters of the signature header instead.