3.3. OAuth

3.3.1. OAuth Overview

This page allows you to generate OAuth signatures using a known good OAuth 1.0a library, https://oauth.net/1/. This way you can quickly identify if 403 Forbidden error received from the Paywize API server is due to a bad signature or another issue.

3.3.2. OAuth Implementation

HMAC-SHA1

Signature computation

Key:
HMAC key value is the concatenation of consumer secret (Merchant control key) + & + token secret (empty string).
For example, it will look as follows:
F9F65098-1111-1111-1111-621611111111&
Request body and data string to sign:
Parameters in request body must be arranged in lexicographical order.
Unique random nonce and current timestamp are generated by merchant.
OAuth parameters must be included in request body (oauth_consumer_key, oauth_nonce, oauth_signature_method, oauth_timestamp, oauth_version).
Whole request is used as data string to calculate HMAC-SHA1 signature.
Signature base string is calculated the following way:
All the request parameters must be percent-encoded and normalized prior to constructing the Signature Base String.
parameterFirst=1&parameterSecond=hello%20there&f=25&f=50&f=a
Encoded Method, URL and Parameters then MUST be concatenated into a single string, encoded and separated by an ‘&’ character, even if empty.
i.e. parameter names and values must be encoded twice, one time while normalizing and another when concatenating final signature base string.
Signature base example: https://oauth.net/core/1.0a/#sig_base_example
Headers and signature:
Authorization header must start from Authorization: OAuth, have oauth_signature and the same OAuth parameters and values as were included in request body (oauth_consumer_key, oauth_nonce, oauth_signature_method, oauth_timestamp, oauth_version).
Merchant login is used as oauth_consumer_key in data string and authorization header, but it is not used as a part of the signing HMAC key.
HMAC-SHA1 signature in binary format must be Base64 and percent-encoded. The signature is sent in request header named ‘Authorization’.
For example, it will look as follows:
9bSeUoR5yJSSJ4KPhotT%2BofEHSQ%3D
Step-by-step signature base string guide:
1. Collect base signature string in the format shown under debugger.
2. Sign it via HMAC-SHA1 with the control key + ‘&’
3. You will get a hex encoded result. Convert from hex to base64.
Authorization header example:
OAuth realm="",
oauth_version="1.0",
oauth_signature_method="HMAC-SHA1",
oauth_consumer_key="merchantlogin",
oauth_timestamp="1513785920",
oauth_nonce="EqINVv5rkhx",
oauth_signature="9bSeUoR5yJSSJ4KPhotT%2BofEHSQ%3D"

Java implementation example

Here is an example of how to make a signed request in Java with help of Scribe library:
public String doPost(String url, Map<String, String>parameters) throws ConnectionIOException, ConnectionTimeoutException {
    OAuthConfig config = new OAuthConfig(apiToken, merchantControlKey, OAuthConstants.OUT_OF_BAND,
            SignatureType.Header, null,null);
    OAuthService service = new OAuth10aServiceImpl(new HmacSha1Mapi(), config);
    OAuthRequest request = new OAuthRequest(Verb.POST,url);
    for (Map.Entry < String,String >;entry :parameters.entrySet()){
        request.addBodyParameter(entry.getKey(), entry.getValue());
    } // empty token for 'two-legged'
    OAuth Token token = new Token("", "");
    service.signRequest(token, request);
    Response response = request.send();
    return response.getBody();
}

private static class Mapi extends DefaultApi {
    @Override
    public String getRequestTokenEndpoint() {
        return null; // not used
    } @Override public String getAccessTokenEndpoint() {
        return null; //not used
    }

    @Override
    public String getAuthorizationUrl(Token requestToken) {
        return null; // not used
    }
    @Override
    public SignatureService getSignatureService() {
        return new HMACSha1SignatureService();
    }
}

C# implementation example

This example shows the C# OAuth implementation for Payout integration:
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Net;

namespace ConsoleApplication1
{
    class Program

    {

        static void Main(string[] args)
        {
            string apiUrl = "https://sandbox.paywize.com/paynet/api/v2/payout/123";

            string consumerKey = "merchantlogin";
            string consumerSecret = "1EF4D28C-1111-2222-3333-444487505555";

            /* for oauth_timestamp */
            TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            string timestamp = Convert.ToInt64(ts.TotalSeconds).ToString();

            /* for oauth_nonce */
            /* (obviously you wouldn't create a new Random class every time!) */
            string nonce = new Random().Next(123400, 9999999).ToString();


            //oauth parameters, need sort alpabetical, use SortedDictionary
            IDictionary<string,string> oauthParams = new SortedDictionary<string, string>();
            oauthParams.Add("oauth_consumer_key",consumerKey);
            oauthParams.Add("oauth_nonce",nonce);
            oauthParams.Add("oauth_timestamp",timestamp);
            oauthParams.Add("oauth_signature_method","HMAC-SHA1");
            oauthParams.Add("oauth_version","1.0");


            //Prepare POST request params , add oauth params
            IDictionary<string,string> requestParameters = new SortedDictionary<string, string>(oauthParams);

            requestParameters.Add("account_number", "1234567890");
            requestParameters.Add("amount", "100");
            requestParameters.Add("bank_branch", "test_branch");
            requestParameters.Add("bank_name", "test_bank");
            requestParameters.Add("client_orderid", "12345");
            requestParameters.Add("currency", "USD");

            StringBuilder builder = new StringBuilder();

            foreach (KeyValuePair<string,string> pair in requestParameters)
            {
                if (builder.Length > 0)
                {
                    builder.Append("&");
                }
                //ATTENTION, call  Uri.EscapeDataString
                builder.Append(Uri.EscapeDataString(pair.Key)).Append("=").Append(Uri.EscapeDataString(pair.Value));
            }
            string normalizedParameters = builder.ToString();



            //create signature base string
            builder = new StringBuilder();

            builder.Append("POST").Append("&");
            builder.Append(Uri.EscapeDataString(apiUrl)).Append("&");
            builder.Append(Uri.EscapeDataString(normalizedParameters));

            string signatureBaseString = builder.ToString();


            // calculate signature, always add & to end consumerSecret
            byte[] signatureKeyBytes = Encoding.UTF8.GetBytes(consumerSecret + "&");

            HMACSHA1 sha1 = new HMACSHA1(signatureKeyBytes);

            /* generate the signature and add it to our parameters */
            byte[] baseStringBytes = Encoding.UTF8.GetBytes(signatureBaseString);
            byte[] baseStringHash = sha1.ComputeHash(baseStringBytes);
            string base64StringHash = Convert.ToBase64String(baseStringHash);
            oauthParams.Add("oauth_signature", base64StringHash);


            //create oauth Header
            string authHeader = createOAuthHeader(oauthParams);

            /* we are ready to send the request! */

            ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(apiUrl);
            request.Headers.Add("Authorization", authHeader);
            var data = Encoding.UTF8.GetBytes(normalizedParameters);
            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded";
            request.ContentLength = data.Length;
            request.Timeout = 30*1000;
            request.UserAgent = "DotNet";

            using (var stream = request.GetRequestStream())
            {
                stream.Write(data, 0, data.Length);
            }

            var response = (HttpWebResponse) request.GetResponse();
            var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd();

            System.Console.WriteLine(responseString);
        }

        private static string createOAuthHeader(IDictionary<string, string> oauthParams)
        {
            StringBuilder builder = new StringBuilder("OAuth realm=\"\"");

            foreach (KeyValuePair<string, string> pair in oauthParams)
            {
                if (builder.Length > 0)
                {
                    builder.Append(",");
                }
                //ATTENTION, call  Uri.EscapeDataString
                builder.Append(string.Format("{0}=\"{1}\"", Uri.EscapeDataString(pair.Key), Uri.EscapeDataString(pair.Value)));
            }

            return builder.ToString();
        }
    }
}

Debug

To reproduce your API call, input all of the data from your original request, including the authentication tokens. Don’t forget to set the nonce and timestamp to the values you used. An OAuth signed URL should match regardless of the generating library. If the signatures differ, you know there is a bug in your OAuth signature code.

HTTP method
URL
parameters
version
consumer key
consumer secret
timestamp
nonce
signature method

normalized parameters
signature base string
signature
authorization header
Curl
              
            

RSA-SHA256

Setup

These steps are required once before making any requests to the API.

Generates a pair of keys: A secret private key to sign the request A public key to verify the request was signed with the private key above is set in our system on endpoint EAV level.

Signature computation

  1. Generate Public and Private key pair

Warning

The private key must be kept secret from everyone.

You need only private and public key to authorize your transaction. To generate it please got to https://www.openssl.org/ download latest version of openssl and run following commands:
openssl genpkey -algorithm RSA -out private_key_pkcs_8.pem -pkeyopt rsa_keygen_bits:4096
openssl rsa -pubout -in private_key_pkcs_8.pem -out public_key.pem
Share your Private key with no one, you should be the only person to know it. Your Public key, on the contrary, should be sent to your manager, so he can register it in the system. Use different keys for production and for testing purposes to avoid its comprometation.
For using this demo you need private key in PKCS#1 container. The format of the key should be PKCS#1 PEM text formatted and unencrypted RSA private key. To get it use the following command
openssl rsa -in private_key_pkcs_8.pem -out private_key_pkcs_1.pem
As a result you will get a key starting with —–BEGIN RSA PRIVATE KEY—–. For production purposes you can use key in any format supported by your software. This demo supports key length up to 4096. Insert your RSA Private key for sandbox environment below.
  1. Request method, url and an array with body parameters are combined into an array, on the basis of which authorization headers are computed(oauth_consumer_key, oauth_nonce, oauth_signature_method, oauth_timestamp, oauth version).
  2. Full request is URL-encoded.

Example of a signed request can be found below:

Debug

To reproduce your API call, input all of the data from your original request, including the authentication tokens. Don’t forget to set the nonce and timestamp to the values you used. An OAuth signed URL should match regardless of the generating library. If the signatures differ, you know there is a bug in your OAuth signature code. Due to current PCI DSS restrictions only OAuth 1.0a RSA-SHA256 signature is allowed. Other signature methods are restricted. So to send command to the server your request should be: sent as POST, contains OAuth 1.0a headers, signed with RSA-SHA256.

Enter your private key in PKCS#1 container to use debug.

Debug form

Fillup mandatory fields (the red ones) with appropriate values. Empty values will not be included in output.

Use either destination-card-no, destination-card-ref-id or destination-iban-no with destination-bic.

URL input URL
login your login should be used as Consumer Public for OAuth
client_orderid make it or use your internal invoice ID
destination-card-no enter the beginning of the sequence, and then "i".
destination-card-ref-id
destination-iban-no
destination-bic
order_desc
amount
currency
ipaddress
first_name
middle_name
last_name
ssn
birthday
address1
city
state
zip_code
country
phone
cell_phone
email
purpose
receiver_first_name
receiver_middle_name
receiver_last_name
receiver_phone
receiver_resident
receiver_identity_document_series
receiver_identity_document_number
receiver_identity_document_id
receiver_address1
receiver_city
redirect_url
redirect_success_url
redirect_fail_url
server_callback_url
merchant_data

Normalized parameters string to sign, according to OAuth 1.0a rules
POST body parameters to submit
OAuth 1.0a headers to submit.
HEX Encoded Signature
* HEX encoded string is for debug purposes only. You shouldn't send this string to the server neither in HEX nor in Encoded HEX representation.
Base64 Encoded Signature
* Binary RSA-SHA256 signature directly encoded in base64 should be sent to the server.

RSA-SHA256 Integration example

This is an example of integration to Paywize server: * PHP

3.3.3. Libraries

We use a simplified flow in which there’s no external authenticator, and there’s no access token and its secret; only the last step of the flow is used.
Merchant login is specified as client identifier (aka consumer key or API token).
A lot of OAuth 1.0a libraries for various programming languages can be found here (last checked 21.12.2017).
Some libraries which may be used: