hmac authentication tools for WAI apps.
authenticate requests made to your WAI apps using HMAC.
wai-hmac-auth
this library provides functions for authenticating HMAC-signed requests in WAI apps. in particular, it provides a function for extracting an api key from a request according to configuration, and a function for verifying that a request is correctly signed by a secret key, according to the configuration. it is designed to be compatible with the Java library jersey-hmac-auth, though possibly more flexible.
how it signs
the authenticate function (as well as the included signRequest function) in effect extracts several elements from the request and concatentates them, and hashes/signs the resulting value. i.e. it constructs the following value from the request, and signs that value.
valueToSign = method + '\n' +
timestamp + '\n' +
(apiKey + '\n')? +
path + ('?' + query) + '\n' +
body
- the method is added directly from the request record.
- the header name used to get the timestamp value can be configured. note that for now, no validation is done on this value; it is only required to be present in the request.
- the api key is only included explicitly if the configuration specifies that the api key is in a header. otherwise (if the api key is in a query parameter), it will be included anyway.
- the value of the path is whatever the application receives in the request record. this means that e.g. if the application is run in the context of a CGI executable, this may not be the same path that the client made the request to. the client will have to account for this when generating the signature.
- the query string is rendered from the queryString using the http-types function renderQuery. the wai-hmac-auth library does not modify these values, but the values received by the app may depend on the server and the middleware. the client will have to account for this properly.
- the entire request body is read, which is why authenticate produces a Request value when successful - this Request value has a requestBody value that will produce the same chunk sequence as the input request value.
the authenticate function also extracts the configured signature header and attempts to decode it as a base64 url-encoded hash digest value as produced by the configured hash function. this represents two possible authentication failures.
finally, the authenticate function compares the passed signature to the signature it generated and fails if they do not match.
how to use
there are basically four steps for using this in your app.
- set up the configuration for how requests to your app will be signed
- when a request comes in, use getApiKey with your spec to find out who the requester is claiming to be
- load up the secret key associated with that identity (api key)
- use that secret key to run the authenticate function on the request
- send back appropriate responses for errors, otherwise continue with the transformed and authenticated request
for example:
-- this would throw an error, i guess
demandApiKey :: IO a
--
informClientOfAuthenticationFailure :: AuthFailure -> IO a
-- imagine a function like this
getSecretOrFail :: ApiKey -> IO SecretKey
handleReq req = do
apiKey <- getApiKey defaultApiKeySpec req >>= maybe demandApiKey return
-- either get a caller reference or fail the request
secretKey <- getSecretOrFail apiKey
authRes <- authenticate defaultRequestConfig req secretKey
req' <- either informClientOfAuthenticationFailure return authRes
-- continue processing using the new request value.