401 Unauthorized error when using C# HttpClient

Hi.

I get an 401 unauthorized error when trying to execute a POST request to the World Check API. I can successfully execute the POST request using Postman. Any help would be appreciated.

My code is as follows:

using Newtonsoft.Json;
using System;
using System.Diagnostics;
using System.Dynamic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace World_CheckAPITester
{
    using System = global::System;
    class Program
    {
        static async Task Main(string[] args)
        {
            await new Program().UsingHttpClient();
        }

        // Combine the data signature and the API secret key to get the HMAC
        // This is the Microsoft HMACSHA256 code copied from the documentation
        private 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);
            string hex = BitConverter.ToString(rawHmac).Replace("-", "");
            
            return (Convert.ToBase64String(rawHmac));
        }

        private async Task UsingHttpClient() {

            try
            {
                HttpClient wc1Client = new HttpClient();
                string apiKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
                string apiSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
                string groupId = "0a3687cf-6c70-198a-9b22-d3fa000009bb";

                string wc1Protocol = "https://";
                string wc1Gatewayhost = "rms-world-check-one-api-pilot.thomsonreuters.com";
                string wc1GatewayUrl = "/v1/";
                string wc1QueryUrl = "cases/screeningRequest";

                string wc1Date = DateTime.UtcNow.ToString("R");

                dynamic postObject = new ExpandoObject();
                postObject.secondaryFields = new string[] { };
                postObject.entityType = "INDIVIDUAL";
                postObject.customFields = new string[] { };
                postObject.groupId = groupId;
                postObject.providerTypes = new string[] { "WATCHLIST" };
                postObject.name = "john smith";

                string postData = JsonConvert.SerializeObject(postObject);
                UTF8Encoding encoding = new UTF8Encoding();
                byte[] byte1 = encoding.GetBytes(postData);

                string dataToSign = "(request-target): post " + wc1GatewayUrl + "cases/screeningRequest\n" +
                "host: " + wc1Gatewayhost + "\n" + // no https only the host name
                "date: " + wc1Date + "\n" + // GMT date as a string
                "content-type: " + "application/json" + "\n" +
                "content-length: " + byte1.Length + "\n" +
                postData;

                string hmac = generateAuthHeader(dataToSign, apiSecret);
                string authorisation = "Signature keyId=\"" + apiKey + "\",algorithm=\"hmac-sha256\",headers=\"(request-target) host date content-type content-length\",signature=\"" + hmac + "\"";

                wc1Client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                wc1Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("basic", authorisation);
                wc1Client.DefaultRequestHeaders.Date = DateTime.UtcNow;

                StringContent postContent = new StringContent(JsonConvert.SerializeObject(postObject), Encoding.UTF8, "application/json");

                HttpResponseMessage httpResponse = await wc1Client.PostAsync(wc1Protocol + wc1Gatewayhost + wc1GatewayUrl + "cases/screeningRequest", postContent);
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.Message + Environment.NewLine + ex.StackTrace);
            }
        }
    }
}



Regards.

Best Answer

  • @gerhardtm,

    Thank you for sharing the details.

    Firstly, I see that there is a difference of more than 30 seconds between request date and response date timestamp in the headers. The time difference of 30 seconds is acceptable for a successful API call. Can you please adjust the system time to NTP and they re-try?

    Secondly, In a POST request the headers would be -

    1. Date

    2. Authorization

    3. Content Length

    4. Content Type

    From the above headers, it looks like only Date and Authorisation is passed as the request headers. Could you please add the content length and the content type as well in the request headers and then try again?

    Please provide us the updated request and response headers along with the request body if you are still getting Error 401 after making the above suggested changes to assist you further.

Answers

  • @gerhardtm,

    Thank you for your query.

    Can you please provide us the request headers and the response headers along with the request body of the failed API call to investigate on the cause of Error 401?

  • Prabhjyot.Mandla

    The request and the response headers as well as the request body generated by sample code I posted are as follows:

    Request headers:
    Accept: application/json
    Authorization: basic Signature keyId="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",algorithm="hmac-sha256",headers="(request-target) host date content-type content-length",signature="CZZr0aVS0jSJTUH2cUd3aKhoLZPN6wT9cBV/QeA45aY="
    Date: Tue, 05 Nov 2019 08:47:34 GMT

    Request Body:

    {
        "secondaryFields": [],
        "entityType": "INDIVIDUAL",
        "customFields": [],
        "groupId": "0a3687cf-6c70-198a-9b22-d3fa000009bb",
        "providerTypes": [
            "WATCHLIST"
        ],
        "name": "john smith"
    }


    Response Headers:
    X-Application-Context: application
    Authorization: WWW-Authenticate: Signature realm="World-Check One API",algorithm="hmac-sha256",headers="(request-target) host date content-type content-length
    Transfer-Encoding: chunked
    Date: Tue, 05 Nov 2019 08:50:02 GMT
    Server: ""

    I suspect that the request headers are incorrect which is why I need some guidance on how to set the request headers on a C# HttpClient object

    Regards


  • Prabhjyot.Mandla

    You said:

    "Firstly, I see that there is a difference of more than 30 seconds between request date and response date timestamp in the headers. The time difference of 30 seconds is acceptable for a successful API call. Can you please adjust the system time to NTP and they re-try?"

    That I can explain, I have set a breakpoint in my code after creating the Httpclient object where the code execution was paused while I copied the header values, which explains the time difference. Apologies for any confusion that might have caused.


    Also, I understand that the Date, Authorization, Content Length and Content Type headers are required, but I'm struggling to set them correctly in my code. Can you please provide a sample of C# code on how to set the request headers on a C# HttpClient object?

    Regards.

  • @gerhardtm,

    I'm afraid, we do not have sample C# code for HttpClient object.