401 Unauthorised WorldCheck One Zero Footprint

Hi I'm using the sample code for WorldCheck One API (https://developers.refinitiv.com/customer-and-third-party-screening/world-check-one-api/downloads) to try replicate for use on WorldCheck One Zero Footprint. However I'm having 401 unauthorised.

The same code, if I replace the api key/secret & url to that of WorldCheck One, works.

Also I've tested my API secret/key URL & groupId using postman, it works.

The one difference I've found between WorldCheck One API & WorldCheck One Zero Footprint from the Postman predefined configuration is this in the pre-request script:

For WorldCheck One:

var dataToSign = "(request-target): post " + environment["gateway-url"] + "cases/screeningRequest\n" +
"host: " + environment["gateway-host"] + "\n" +
"date: " + date + "\n" +
"content-type: " + environment["content"] +"\n" +
"content-length: " + contentLength + "\n" +
content;

For WorldCheck One Zero Footprint:

var dataToSign = "(request-target): post " + environment["zfs-gateway-url"] + "cases/screeningRequest\n" +
"host: " + environment["zfs-gateway-host"] + "\n" +
"date: " + date + "\n" +
"content-type: " + environment["content"] +"\n" +
"content-length: " + contentLength;

The Zero footprint one doesn't seem to append "content" at the end.

This is the C# code from (https://developers.refinitiv.com/customer-and-third-party-screening/world-check-one-api/downloads) which I'm trying to adapt to use for Zero Footprint. Can anyone point me in the right direction as to where I'm going wrong?

   DateTime dateValue = DateTime.UtcNow; // get the datetime NOW GMT


string date = dateValue.ToString("R"); // WC1 header requires GMT datetime stamp
//Console.WriteLine(date);
//set host and credentials to the WC1 API Pilot server WC1SampleClientAPI account
string apikey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
string apisecret = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
string gatewayurl = "/v1/";
string gatewayhost = "zfs-world-check-one-api-pilot.thomsonreuters.com"; string requestendpoint = "https://zfs-world-check-one-api-pilot.thomsonreuters.com/v1/cases/screeningRequest";


string postData = "{ \"groupId\":\"XXXXXXXXXXXXXXXXXXXXXX\", \"entityType\": \"INDIVIDUAL\", \"providerTypes\": [ \"WATCHLIST\" ], \"name\": \"Smith\", \"secondaryFields\":[] }";


//Console.WriteLine(postData.Length);


string msg = postData;
//Console.WriteLine(msg);
UTF8Encoding encoding = new UTF8Encoding();
byte[] byte1 = encoding.GetBytes(postData);


// Assemble the POST request - NOTE every character including spaces have to be EXACT
// for the API server to decode the authorization signature
string dataToSign = "(request-target): post " + gatewayurl + "cases/screeningRequest\n" +
"host: " + gatewayhost + "\n" + // no https only the host name
"date: " + date + "\n" + // GMT date as a string
"content-type: " + "application/json" + "\n" +
"content-length: " + byte1.Length + "\n" +
msg;


Console.WriteLine("---api secret---");
Console.WriteLine(apisecret);
Console.WriteLine("---dataToSign---");
Console.WriteLine(dataToSign);
Console.WriteLine("string hmac = generateAuthHeader(dataToSign, apisecret);");
// The Request and API secret are now combined and encrypted
string hmac = generateAuthHeader(dataToSign, apisecret);


// Assemble the authorization string - This needs to match the dataToSign elements
// i.e. requires host date content-type content-length
//- NOTE every character including spaces have to be EXACT else decryption will fail with 401 Unauthorized
string authorisation = "Signature keyId=\"" + apikey + "\",algorithm=\"hmac-sha256\",headers=\"(request-target) host date content-type content-length\",signature=\"" + hmac + "\"";






Console.WriteLine("---Hmac---");
Console.WriteLine(hmac);
//Console.WriteLine(authorisation);


// Send the Request to the API server
HttpWebRequest WebReq = (HttpWebRequest)WebRequest.Create(requestendpoint);
// Set the Headers
WebReq.Method = "POST";
WebReq.Headers.Add("Authorization", authorisation);
WebReq.Headers.Add("Cache-Control", "no-cache");
WebReq.ContentLength = msg.Length;
WebReq.Date = dateValue; // use datetime value GMT time
// Set the content type of the data being posted.
WebReq.ContentType = "application/json";
WebReq.ContentLength = byte1.Length;


Stream newStream = WebReq.GetRequestStream();
newStream.Write(byte1, 0, byte1.Length);


// Get the Response - Status OK
HttpWebResponse WebResp = (HttpWebResponse)WebReq.GetResponse();
// Status information about the request
Console.WriteLine(WebResp.StatusCode);
Console.WriteLine(WebResp.ResponseUri);


// Get the Response data
Stream Answer = WebResp.GetResponseStream();
StreamReader _Answer = new StreamReader(Answer);
string jsontxt = _Answer.ReadToEnd();


// convert json text to a pretty printout
var obj = Newtonsoft.Json.JsonConvert.DeserializeObject(jsontxt);
var f = Newtonsoft.Json.JsonConvert.SerializeObject(obj, Newtonsoft.Json.Formatting.Indented);
Console.WriteLine(f);
Console.WriteLine("Press any key");
Console.ReadKey(); // pause for any key
}


public static string generateAuthHeader(string dataToSign, string apisecret)
{
byte[] secretKey = Encoding.UTF8.GetBytes(apisecret);
HMACSHA256 hmac = new HMACSHA256(secretKey);
hmac.Initialize();


byte[] bytes = Encoding.UTF8.GetBytes(dataToSign);
byte[] rawHmac = hmac.ComputeHash(bytes);
Console.WriteLine("---rawHmac---");
string hex = BitConverter.ToString(rawHmac).Replace("-", "");
Console.WriteLine(hex);
return (Convert.ToBase64String(rawHmac));
}

Best Answer

  • contentlength.png@yusuf.fauzel

    I see that you're passing the request payload as part of the headers in your code (i.e. msg)

    pwc.png when you're forming the dataToSign variable, you're adding the payload(msg) along with the content length which shouldn't be the case(see attached pre-request script), can you please pass the request payload as a normal payload and not send it as part of the request headers.

    Secondly, I see that you're calculating the string length and then UTF-8 encoding and again calculating its length , I believe calculating it only once after it's encoded will do the job.

    Please try by eliminating the payload from the headers and let me know your findings.

    Thanks for your patience.

Answers

  • @yusuf.fauzel

    Hi,

    I believe the API key & Secret you have is to access the World-Check One API (which works), but let me help you understand that you cannot use the same keys to access Zero Footprint API, if your product of interest is World-Check One ZFS, please download the postman collection for the ZFS and get in touch with your sales specialist who will be able to get you the trial access for Zero Footprint API instead of World-Check One which explains why you're observing a 401 Unauthorized response.

    Regards,

    Mehran Khan

  • @yusuf.fauzel

    Can you please provide me the complete request and response of the 401 Unauthorized case?

    Regards,

    Mehran Khan

  • Hi Mehran,

    I already have a different key & secret for World Check One Zero Footprint. I also downloaded the Postman collection for ZFS & it works on Postman.

    The issue I'm having is when trying on C#, I'm getting 401 unauthorized, despite using the right credentials for ZFS.

    I suspect the issue is with the dataToSign function. I see that on ZFS it doesn't append content at the end. See attached image.

    image

  • @yusuf.fauzel

    Thanks for the clarification , I am looking into it , I will get back to you shortly.

    Regards,

    Mehran Khan

  • @Mehran.Ahmed Khan

    The headers from the C# application (I have masked the start of the groupId):

    POST https://zfs-world-check-one-api-pilot.thomsonreuters.com/v1/cases/screeningRequest HTTP/1.1
    Authorization: Signature keyId="080d9f9e-2863-462f-a6b6-ccf28d322485",algorithm="hmac-sha256",headers="(request-target) host date content-type content-length",signature="CwByRxCFZ5b52j0fEmxb2t5sCwHzzH+n+ht16s6pvM4="
    Cache-Control: no-cache
    Date: Wed, 20 Mar 2019 12:36:03 GMT
    Content-Type: application/json
    Host: zfs-world-check-one-api-pilot.thomsonreuters.com
    Content-Length: 153
    Expect: 100-continue
    Connection: Keep-Alive
    { "groupId":"***********4f7", "entityType": "INDIVIDUAL", "providerTypes": [ "WATCHLIST" ], "name": "Smith", "secondaryFields":[] }

    Response:

    HTTP/1.1 401 Unauthorized
    Content-Type: application/json
    Content-Length: 2
    Connection: keep-alive
    Date: Wed, 20 Mar 2019 12:36:28 GMT
    x-amzn-RequestId: c834e04c-4b0c-11e9-b59a-959f6f65742e
    x-amzn-ErrorType: AccessDeniedException
    x-amz-apigw-id: W10H6F9MjoEFvvw=
    X-Cache: Error from cloudfront
    Via: 1.1 2a3894d93a2a1e3b94fb6ed07542ad37.cloudfront.net (CloudFront)
    X-Amz-Cf-Id: da27aJfDk3-tkXBTBvqXYDSPbpRtYRdvnPOxXWhW_9LyB8HIPT3oYQ==
    []
  • @Mehran.Ahmed Khan Any updates re. the above?

  • Hi @Mehran.Ahmed Khan,

    Thank you for all your help. I managed to fix the issue with your pointers. The final error which was causing the API to fail was this:

    image

    I didn't remove the newline after I removed the payload from the dataToSign.