401 Authorization error only with POST method to World Check API

I can call any GET method on the Refinitiv World Check API and everything is signed and works okay but when I try to POST a Case, I get a 401 error. It works fine from Postman using my key and secret but my C# code doesn't. I have tried replacing the JSON content just with "{}" but it seems to be the authorisation failing before the server tries to process my message. This my code that creates the authorisation:


authorisecontent.png


This is code that does the Hmac:


encodehmac.png


The GET and POST methods are identical and use common code apart from when set the content elements of the data to sign and obviously when we set the request message content.


Here is my actual code for the POST which uses the GET signature first then adds its own parms:

        var byteLength = new UTF8Encoding().GetBytes(jsonContent).Length;

var dataToSign = MakeCommonSignedData(httpMethod, localPath, utcDate)
+ "\n" +
$"content-type: {ContentTypeJson}\n" +
$"content-length: {byteLength}\n" +
jsonContent;

var hmac = EncodeHmac256(dataToSign);

return $"Signature keyId=\"{config.ApiKey}\",algorithm=\"hmac-sha256\",headers=\"(request-target) host date content-type content-length\",signature=\"{hmac}\"";


Here is the logged data to sign:

(request-target): post /v2/cases
host: api-worldcheck.refinitiv.com
date: Sat, 11 Mar 2023 16:14:09 GMT
content-type: application/json
content-length: 295
{
"note": null,
"groupId": "5jb8a2z6imev1h93zc1zzooof",
"entityType": "INDIVIDUAL",
"caseId": "49db5529-b50c-4459-b1d3-ab52558eec58",
"name": "Rob Kent",
"nameTransposition": false,
"providerTypes": [
"WATCHLIST"
],
"customFields": [],
"secondaryFields": []
}
Signature keyId="XXXXXXXXXXXXXX",algorithm="hmac-sha256",headers="(request-target) host date content-type content-length",signature="gbSDLGSAtfK8+fdvi9i56UduGGARVMLXpgRg8DNLLrs="

Here is my code that calls the above and sends the message:

        var utcNow = DateTime.UtcNow;

var requestMessage = new HttpRequestMessage(new HttpMethod(httpMethod), url);

var jsonContent = jsonBody == null ? "" : JsonObjectBase.Serialize(jsonBody);
//var jsonContent = httpMethod == httpGet ? null : "{}";

var authorisation = MakeAuthorisation(httpMethod, localPath, utcNow, jsonContent);

requestMessage.Headers.Date = utcNow;
requestMessage.Headers.Add("Authorization", authorisation);
requestMessage.Headers.Add("Accept", ContentTypeJson);
requestMessage.Headers.Add("Cache-Control", "no-cache");

if (jsonContent.HasValue())
{
logger.Debug($"{httpMethod} JSON content:\r\n{jsonContent}");
requestMessage.Content = new StringContent(jsonContent, Encoding.UTF8, ContentTypeJson);
}

return await SendRequestAndParseResponseString(requestMessage);

Apparently the HttpClient creates the content-type and content-length headers automagically when you create the body content in that way.

I may have code-blindness from looking at it too long so if anyone can see anything wrong, I would be most grateful.

Best Answer

  • rob.kent
    rob.kent Newcomer
    Answer ✓

    This was a very obscure problem that was solved by the Refinitiv team who did a Teams session with me and spotted the issue after we captured the output in Fiddler.

    The issue is that the Refinitiv C# sample uses the older HttpWebRequest class and I am using the HttpClient library. When you set the Content with HttpWebRequest, you do this:

    WebReq.ContentType = "application/json";
    WebReq.ContentLength = byte1.Length;
    Stream newStream = WebReq.GetRequestStream();
    newStream.Write(byte1, 0, byte1.Length);

    So you have set the ContentType explicitly.

    With HttpClient, you do this:

    requestMessage.Content = new StringContent(jsonContent, Encoding.UTF8, "application/json);

    This automagically produces a request header of:

    Content-Type: application/json; charset=utf-8

    This no longer matches what you put in your data to sign:

    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"+
    postData;

    So to fix it you have to change your data to sign so that it matches the actual content header, as follows:

    // The content-type line must match exactly what the HttpClient creates when you set the Content.
    // This is different to the Refinitiv sample code which uses the old WebRequest not HttpClient.
    $"content-type: application/json; charset=utf-8\n" +
    $"content-length: {byteLength}\n" +
    jsonContent;

    When your signed data matches exactly the specified headers, the authorization error should go away.

Answers