Signing API Requests
This page will help you understand the details of the API request signature algorithm you need to implement, and it will also provide you with a sample Postman script to assist you further.
To sign your API request, you need to:
1. Generate a signed JWS token
We strongly suggest that you use one of the existing cryptographic libraries to generate a JWS token. In our Postman example we used the "jsrsasign" library.
The JWS token must be generated using all the below input parameters:
- Private Key
- JWT Header:
alg
(ES256
)kid
(public key id)typ
(JWT
)
- JWT Payload:
method
(request method: GET/POST/DELETE)path
(full path of the request)query
(query string)sha256
(SHA256 hashed value of the request body) . This is optional and will be null for GET/DELETE requestsiat
(issued at time)exp
(token expiry time. To avoid replay attacks, ensure this value is within 60 seconds ofiat
)
2. Add the JWS token to the HTTP header
To add the generated JWS token into the HTTP header you must use the Authorization header parameter, see example below.
Authorization: JWS <JWStoken>
3. Pass Wpay signature verification
The payload used to generate the signature must match the payload sent to Wpay. Any formatting issues (e.g. extra spaces, extra new lines, case mismatch) will lead to a different hash value resulting in a 401 unauthorised access error.
- If the Signature verification fails, a 401 unauthorized error is returned.
- If the Signature verification succeeds but the requestor does not have permission to access the API, a 403 forbidden error is returned.
Furthermore to avoid replay attacks, we verify the request against our internal server time as follows:
- the request issued at time must be less than or equal to our internal server time
- the request token expiry time must be greater or equal to our internal server time
If any of the above fail, a 401 unauthorized error is returned.
Sample Postman Script
This is a sample Postman pre-request script for generating and adding the JWS signature to the API request header. Note that the following environment variables need to be set before running this snippet:
publicKeyId
- set the public key Id provided by WpayjwkPrivateKey
- generate JWK key from the private key using tools like JWK Generator and set the value
// #region LOAD jsrsasign
var navigator = {};
var window = {};
//load jsrsasign
pm.sendRequest("http://kjur.github.io/jsrsasign/jsrsasign-latest-all-min.js", (err, res) => {
pm.environment.set("jsrsasign", res.text());
eval(pm.environment.get("jsrsasign"));
// #endregion
// #region header
var header =
{
"alg":"ES256",
"kid": pm.environment.get("publicKeyId"),
"typ":"JWT"
};
// #region header
// #region Payload
var cryptoJS = require("crypto-js");
var hashedContent = null;
//remove comments and trim
var strippedData = pm.request.body?.raw?.replace( /\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g, (m, g) => g ? "" : m).trim();
if(strippedData?.length > 0) {
//replace variables with actual values
const resolvedBody = JSON.stringify(JSON.parse(pm.variables.replaceIn(strippedData)));
pm.request.body.update(resolvedBody);
//create hash from body
hashedContent = cryptoJS.SHA256(resolvedBody).toString(cryptoJS.enc.Base64);
}
//sort query string
let queryString = pm.request.url.getQueryString();
if(queryString.length > 0)
{
let queryParams = queryString.split('&').map(param => param.split('='));
queryParams.sort((a,b) => a[0].localeCompare(b[0]));
queryString = queryParams.map(param => param.join('=')).join('&');
}
var payload =
{
"method":pm.request.method,
"path": pm.variables.replaceIn(pm.variables.replaceIn(pm.request.url).toString()),
"query":queryString.length > 0 ? queryString: null,
"sha256":hashedContent
};
addIATAndEXP();
// This function adds the Issued At and Expiry for the signature. This ensures that the token
// have shorter lifetime and helps in stopping replay attacks
function addIATAndEXP() {
var iat = Math.floor(Date.now() / 1000);
payload.iat = iat;
payload.exp = iat + 60; //exp should not be more than iat + 60s
}
//#endregion
//create signature from header and payload
const jwkPrivateKey = JSON.parse(pm.environment.get("jwkPrivateKey"));
const jwsSignature = KJUR.jws.JWS.sign("ES256", header, payload, jwkPrivateKey);
//add jwsSignature as Authorization header
pm.request.headers.add({
key: "Authorization",
value: "JWS " + jwsSignature
});
});
The sections below provide an explanation for each part of this Postman script.
Library
Postman needs a library to generate the JWS signature. This script uses the jsrsasign
library, and the below part of the script is used to load this library in Postman. This library is not needed to be loaded for every API request.
Other libraries/packages are available to generate the signatures for other environments (Java/node) . E.g. npm: jose
// #region LOAD jsrsasign
var navigator = {};
var window = {};
//load jsrsasign
pm.sendRequest("http://kjur.github.io/jsrsasign/jsrsasign-latest-all-min.js", (err, res) => {
pm.environment.set("jsrsasign", res.text());
eval(pm.environment.get("jsrsasign"));
// #endregion
Header
This part of the script is used to create the JWT Header values:
- algorithm is ES256 (Elliptic Curve Digital Signature Algorithm with the P-256 curve and the SHA-256 hash function)
publicKeyId
is the Wpay generated public key Idtyp
is the Token type. In this case, it will beJWT
// #region header
var header =
{
"alg":"ES256",
"kid": pm.environment.get("publicKeyId"),
"typ":"JWT"
};
// #region header
Payload
This part of the script is used to create the JWT Payload that contains the following values:
method
- request method (GET/POST/DELETE) frompm.request.method
path
- full path of the request url after resolving any variable namesquery
- query parameter string should be sorted alphabetically in the formatbar=1&baz=2&foo=3
sha256
- SHA256 hash value for the request body- Loads
crypto-js
library that will be used to generate SHA256 hash value - Removes any comments and trims the request body
- replaces all the variable names with actual values (
pm.variables.replaceIn
) - updates the request body with the resolved request body
pm.request.body.update(resolvedBody)
- generates the
SHA256
value
- Loads
iat
- Token Issued At timestamp. This should be in Unix epoch (Unix timestamp)exp
- Token expiry timestamp. This should not be more than iat + 60s.
// #region Payload
var cryptoJS = require("crypto-js");
var hashedContent = null;
//remove comments and trim
var strippedData = pm.request.body?.raw?.replace( /\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g, (m, g) => g ? "" : m).trim();
if(strippedData?.length > 0) {
//replace variables with actual values
const resolvedBody = JSON.stringify(JSON.parse(pm.variables.replaceIn(strippedData)));
pm.request.body.update(resolvedBody);
//create hash from body
hashedContent = cryptoJS.SHA256(resolvedBody).toString(cryptoJS.enc.Base64);
}
//sort query string
let queryString = pm.request.url.getQueryString();
if(queryString.length > 0)
{
let queryParams = queryString.split('&').map(param => param.split('='));
queryParams.sort((a,b) => a[0].localeCompare(b[0]));
queryString = queryParams.map(param => param.join('=')).join('&');
}
var payload =
{
"method":pm.request.method,
"path": pm.variables.replaceIn(pm.variables.replaceIn(pm.request.url).toString()),
"query":queryString.length > 0 ? queryString: null,
"sha256":hashedContent
};
addIATAndEXP();
// This function adds the Issued At and Expiry for the signature. This ensures that the token
// have shorter lifetime and helps in stopping replay attacks
function addIATAndEXP() {
var iat = Math.floor(Date.now() / 1000);
payload.iat = iat;
payload.exp = iat + 60; //exp should not be more than iat + 60s
}
//#endregion
Signature
This part of the script is used to generate the JWS signature using header
, payload
and jwkPrivateKey
(using the KJUR
library).
jwkPrivateKey
is the JWK format of the private key that was generated at the beginning.
The generated signature is added as the Authorization header for each request.
//create signature from header and payload
const jwkPrivateKey = JSON.parse(pm.environment.get("jwkPrivateKey"));
const jwsSignature = KJUR.jws.JWS.sign("ES256", header, payload, jwkPrivateKey);
//add jwsSignature as Authorization header
pm.request.headers.add({
key: "Authorization",
value: "JWS " + jwsSignature
});
});
Updated 6 months ago