OPA api uses HMAC(Hash-based message authentication code) authentication and merchant needs to call these apis using HMAC authentication. HMAC is a message authentication code that uses a key in conjunction with a hash function. For the implementation of HMAC based authentication, entire request is taken into account for building authentication object. The client(merchant) creates HMAC authentication code based on the below documentation and sets it in the HTTP "Authorization" request header. On receiving the request, the server(OPA) recreates this authentication code and request is executed successfully if both codes are matched otherwise unauthorized response will be returned.
Value | Description | Example |
---|---|---|
API Key | API key generated | APIKeyGenerated |
API Key secret | API Key secret generated | APIKeySecretGenerated |
Request URI | Service request URL path | /v2/codes |
Request method | HTTP method | POST |
Epoch | Current epoch seconds, Note : Validated whether less than 2mins on server side | 1579843452 |
Nonce | Random generated stringNote : String of any length, but recommended to use of length 8 | acd028 |
Request body | Value of request body passed in request | {"sampleRequestBodyKey1":"sampleRequestBodyValue1","sampleRequestBodyKey2":"sampleRequestBodyValue2"} |
Request content type | Content type passed in request header | application/json;charset=UTF-8; |
hash (MD5(Request body, Request content type)) | Hash of request body and content type. This will be the output from step 1 of genererating HMAC auth header added below | 1j0FnY4flNp5CtIKa7x9MQ== |
Sample HMAC Auth header generated from above example values is as followed
hmac OPA-Auth:APIKeyGenerated:NW1jKIMnzR7tEhMWtcJcaef+nFVBt7jjAGcVuxHhchc=:acd028:1579843452:1j0FnY4flNp5CtIKa7x9MQ==
Step 1: Hash the body and content-type with MD5 algorithm
Code sample for hashing body and content type
private String requestBody;
private String contentType;
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(contentType.getBytes(StandardCharsets.UTF_8));
md.update(requestBody.getBytes(StandardCharsets.UTF_8));
String hash = new String(
Base64.getEncoder().encode(md.digest()),
StandardCharsets.UTF_8);
Note : If there is no request body, for instance, the HTTP GET method case, no need of generating MD5. Instead hash value is set as "empty".
Step 2 : Build string to be hashed with HMAC-SHA256.
We will consider following parameters for signing the request
private String requestUrl; //Only the request URI Example: "/v2/codes/payments/dynamic-qr-test-00002"
private String httpMethod;
private String nonce; //Random string
private String epoch;
private String contentType;
private String hash; //Output of step 1
private static final String DELIMITER = "\n";
byte[] hmacData = new StringBuilder()
.append(requestUrl)
.append(DELIMITER)
.append(httpMethod)
.append(DELIMITER)
.append(nonce)
.append(DELIMITER)
.append(epoch)
.append(DELIMITER)
.append(contentType)
.append(DELIMITER)
.append(hash != null ? hash : "")
.toString()
.getBytes(StandardCharsets.UTF_8);
Note : If there is no request body, for instance, the HTTP GET method case, content-type and hash will be set as "empty".
Step 3 : Generating HMAC object Generate HMAC based on output from Step 2 and Key secret of API key secret pair.
public final String toBase64HmacString() {
private String apiKeySecret;
private byte[] dataToSign; //Output from step 2
try {
SecretKeySpec signingKey = new SecretKeySpec(apiKeySecret.getBytes(StandardCharsets.UTF_8),
"HmacSHA256");
Mac sha256HMAC = Mac.getInstance("HmacSHA256");
sha256HMAC.init(signingKey);
byte[] rawHmac = sha256HMAC.doFinal(dataToSign);
return java.util.Base64.getEncoder().encodeToString(rawHmac);
} catch (GeneralSecurityException e) {
LOGGER.error("Unexpected error while creating hash: " + e.getMessage());
throw new IllegalArgumentException(e);
}
}
Step 4 : Build header object with the parameters of syntax. //We will be using password as the key to sign the hmacData
String authHeader = "hmac OPA-Auth:" + api-key +
":" + macData + ":" + nonce + ":" + epoch + ":" + hash;
Note: macData is output from step 3 andFor nonce and epoch, same value that has been used in step 2 should be passed.
If there is no body of the request such as in the case of HTTP GET method, the hash value will be "empty", so the header object will be as follows.
String authHeader = "hmac OPA-Auth:" + api-key +
":" + macData + ":" + nonce + ":" + epoch + ":empty";
The value of authHeader is passed in HttpHeader.AUTHORIZATION. With the authHeader will decode back the data added and with the HTTP request object and based on data available for api-key in the system, we will recreate the SHA256("key", requestParams) which gives macData. This macData is verified against the value passed in the header.