HTTP request signing
This is a simplified and opinionated HTTP request signing scheme for use in communication between HTTP APIs.
The module provides both the server middleware and client functionality.
$ npm install reqsign --save
const app = require('express')();
const bodyParser = require('body-parser-of-your-choice');
const reqsign = require('reqsign');
const reqsignOptions = {
clockSkew: 300,
keyRetriever (login) {...},
replayAttackDefender (login, signature) {...}
};
// Mount the middleware
app.use(bodyParser());
app.use(reqsign.server(reqsignOptions));
// Use it
app.get('/some_path', (req, res) => {
const isRequestAuthenticated = req.user.isAuthenticated;
const userLogin = req.user.login;
// etc
});
const reqsign = require('reqsign');
const reqsignOptions = {
login: 'my_service_login',
key: 'secret'
};
const req = reqsign.client(reqsignOptions);
const data = {
prop1: 'value1',
prop2: 'value2'
};
req.post('url', data)
.then(response => {
// Make use of response.resStatus and response.resBody
})
.catch(err => {
// Process the err gracefully
});
TimeStamp = GET-TIMESTAMP()
TimeStamp is the current system time UNIX timestamp of the client machine including milliseconds (e.g., 1465564560647).
The client has to build the StringToSign
following the following pattern:
StringToSign = <TimeStamp> + "\n" + <ContentString>
<ContentString>
is a string formed depending of the request type.
If the request contains any payload (Content-Length HTTP header is present and its value is greater than 0), ContentString = MD5(<PayloadRawString>)
.
If the request contains no payload (Content-Length HTTP header is not present or its value is 0), the ContentString
is formed using the following algorithm:
- All URL query parameters are sorted in alphabetical order ascending.
ContentString =
URI-ENCODE(<QueryParameter1>) + "=" + URI-ENCODE(<value>) + "&" +
URI-ENCODE(<QueryParameter2>) + "=" + URI-ENCODE(<value>) + "&" +
...
URI-ENCODE(<QueryParameterN>) + "=" + URI-ENCODE(<value>)
Signature = BASE64(HMAC-SHA256(<StringToSign>, <key>))
<key>
is a secret key shared between the client and the server.
The client sends the Authorization
HTTP header with the request:
Authorization: Signature timestamp={Int} login={String} signature={String}
The options object:
{
"clockSkew": "{Integer}",
"keyRetriever": "{Function}",
"replayAttackDefender": "{Function}"
}
A clock skew value in seconds that compensate possible differences between the server and client machines system time. The clock skew is applied in both directions (e.g., clockSkew = 300s means 300s in the past AND 300s to the future).
It is not feasible to set this parameter to 0 or a very small value as in this case all requests will be treated as expired. Hence, the default value is 300
, and the minimum value is 60
.
A function that retrieves the secret key for the given login
value that the client uses to sign requests. Mechanism of storing the login-key pairs is out of scope for reqsign
.
Parameters:
{String} login
- The login value sent by the client inAuthorization
HTTP header.
Returns {Promise} resolve(result), reject(err)
:
-
{String | Object | null} result
- Ifresult
is aString
, this is the key associated with thelogin
to be used to verify the signature. Ifresult
is anObject
, it must have the following properties:{String} key
;{Array<String>} roles
- a collection of roles associated with thelogin
;
-
{Error} err
- An error object. Note that absence of akey
for givenlogin
is not an error condition - in this situation the promise has to resolve with thenull
value.
OPTIONAL. A function that might be used to protect the server against the Replay Attack. The implementation of protection (what the function does) is out of scope for reqsign
.
Parameters:
{String} login
- Login value derived from the Authorization header;{String} signature
- Signature value derived from the Authorization header.
Returns: {Promise} resolve(isOk) reject(err)
:
{Boolean} isOk
-true
if the request is not replayed; otherwise -false
;{Error} err
- An error object.
reqsign
middleware extends the req
object with user
property and passes the request down the middleware pipe.
-
{Boolean} req.user.isAuthenticated
-
{String} req.user.login
-
{Array<String>} req.user.roles
-
{String | null} req.user.errorCode
- Error code as follows:WRONG_REQUEST
- The request received does not comply with the scheme (e.g., wrong format of the Authorization header, or its absence etc).NO_KEY
- A key to verify the signature.EXPIRED
- The request expired.WRONG_SIGNATURE
- Signature verification failed.REPLAYED
- The request is replayed.
Use a function returned by reqsign.client(options)
call to make signed HTTP requests to third-party APIs.
The options object:
{
"login": "{String}",
"key": "{String}"
}
The login your application is registered with by a third-party API.
The key/password shard by your application and a third-party API.
Returns an instance of the API client you use to make signed HTTP requests to APIs.
const reqsign = require('reqsign');
const opts = {
login: 'my_login',
key: 'shared_key'
};
const req = reqsign(opts);
Method to make a GET request.
Parameters:
{String} url
- Target URL (without query parameters).{Object} data
- Data to be sent in request parameters.
Return: Promise<Object>
- Promise that resolves with an object with properties:
{Integer} resStatus
- Response HTTP status code.{Any} resBody
- Response payload.
Method to make a POST request.
Parameters:
{String} url
- Target URL (without query parameters).{Object} data
- Data to be sent as a JSON object in request body.
Return: Promise<Object>
- Promise that resolves with an object with properties:
{Integer} resStatus
- Response HTTP status code.{Any} resBody
- Response payload.