1

I am new to the C# world, and can't for the life of me figure out how to get around this error. I am trying to make a simple get request using a platform API key. I have built out the API connection in Google App Script on the same laptop, and it works fine, but when trying to build out the same API in C#, it is returning:

{StatusCode: 403, ReasonPhrase: 'Forbidden', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:
{
  Date: Sun, 13 Mar 2022 02:41:29 GMT
  Transfer-Encoding: chunked
  Connection: close
  CF-Chl-Bypass: 1
  Permissions-Policy: accelerometer=(),autoplay=(),camera=(),clipboard-read=(),clipboard-write=(),fullscreen=(),geolocation=(),gyroscope=(),hid=(),interest-cohort=(),magnetometer=(),microphone=(),payment=(),publickey-credentials-get=(),screen-wake-lock=(),serial=(),sync-xhr=(),usb=()
  Cache-Control: private, max-age=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0
  X-Frame-Options: SAMEORIGIN
  Expect-CT: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
  Strict-Transport-Security: max-age=2592000
  Server: cloudflare
  CF-RAY: 6eb1692f8bd776c3-LHR
  Content-Type: text/html; charset=utf-8
  Expires: Thu, 01 Jan 1970 00:00:01 GMT
}}

The API documentation says:

"To authenticate against the API, include your API key in the 'Authorization' header, prefixed with 'Key ', in every request. Example: 'Authorization: Key yourapikey'"

And so, I have tried adding this to
a) the HttpClient via HttpClient.DefaultRequestHeaders.Authorization
b) the HttpClient via HttpClient.DefaultHeaders.Add
c) the HttpRequestMessage via HttpRequestMessage.Headers.Add
In each instance, the request URI looks good, as well as the headers, but still returning 403.
My current structure is:

// services
builder.Services.AddHttpClient("myplatform", c =>
{
    c.BaseAddress = new Uri("https://seller-api.myplatform.com/v2/");
    c.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
});

// controller
// constructor uses IHttpClientFactory
this._httpClient = clientFactory.CreateClient("myplatform");

// service
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Key", platformKey);

string endPoint = "offers" + CreateQueryString(pageNumber, pageSize);
// example endPoint: offers?page_number=1&page_size=100

var requestMsg = new HttpRequestMessage()
{
     Method = HttpMethod.Get,
     RequestUri = new Uri(httpClient.BaseAddress + endPoint)
};

try
{    
     var result = await httpClient.SendAsync(requestMsg);
}
catch (Exception ex)
{
                    
}

Again, the content of the call works when calling through GoogleAppScript. What am I doing wrong in C#, and how can I correct this?

Not sure if this is all the information needed - let me know otherwise! Important to note, the target framework is .NET 6.0.

EDIT

As suggested by a comment around duplicate clients, I have removed the client factory. I am now creating a new HttpClient in the controller constructor, and passing this client to my service to do the GET request.

this._httpClient = new HttpClient();

Again, the client and the request message look well formed at time of request but still returning 403 error. Is there an issue with my VS22 client, or web client etc.?

Also, the call I am making successfully via Google AppScript is using UrlFetchApp. Not sure what is the issue here with the C# side..

EDIT2

Adding current GAS code for reference:

var url = 'https://seller-api.platform.com/v2';
var end_point = '/offers?';
var header = {
    'Authorization': api_key
  }
var params = {
    'method': 'GET',
    'headers': header
  }

// call API
var page_query = 'page_number=' + page + '&page_size=' + maxItemsPerPage;
var full_url = url + end_point + page_query;

var response = UrlFetchApp.fetch(full_url, params);
Dean
  • 2,326
  • 3
  • 13
  • 32
  • it looks like you are using multiple http clients. please try to detangle that. – Daniel A. White Mar 13 '22 at 03:17
  • @DanielA.White if I set `_httpClient = new HttpClient()` in my controller constructor, and set the `.BaseAddress` and `.DefaultRequestHeaders.Accept.Add` in the controller method, I get the same `403 Forbidden` response. – Dean Mar 13 '22 at 03:21
  • it still looks like you are using multiple ones - even thru the client factory. its really hard to follow. – Daniel A. White Mar 13 '22 at 14:12
  • @DanielA.White I am not sure I follow. I remove the client factory and create a new client in my controller constructor... That then gets passed to my service method... Where should the client be defined? – Dean Mar 13 '22 at 16:03
  • 2
    Use [Filddler](https://www.telerik.com/fiddler/fiddler-classic) to examine your HTTP request and compare it with the working one. – David Browne - Microsoft Mar 13 '22 at 21:49

1 Answers1

0

The HTTP 403 Forbidden response status code indicates that the server understands the request but refuses to authorize it.

There are two ways add request headers when using HttpClient:

  • Add headers for all requests using HttpClient.DefaultRequestHeaders.
        HttpClient = new HttpClient();
        HttpClient.DefaultRequestHeaders.Add("Key", platformKey);
        var response = await HttpClient.GetAsync(GetRandomNumberUrl);
        response.EnsureSuccessStatusCode();
        await response.Content.ReadAsStringAsync();
  • Add headers per request using HttpRequestMessage.Headers.
        HttpClient = new HttpClient();
        using (var request = new HttpRequestMessage(HttpMethod.Get, randomNumberUrl))
        {
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", platformKey);
            var response = await HttpClient.SendAsync(request);

            response.EnsureSuccessStatusCode();

            await response.Content.ReadAsStringAsync();
        }

Your problem is here in this line:

httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Key", platformKey);

your _httpClient and Authorization httpClient is not same instance.

Try this way:

                HttpClient HttpClient = new HttpClient();
                string url = "https://seller-api.platform.com/v2";
                string end_point = "/offers?";
                string api_key = "key here";
                string page_query = "page_number=" + 10 + "&page_size=" + 20;
                string full_url = url + end_point + page_query;

                using (var request = new HttpRequestMessage(HttpMethod.Get, url))
                {
                    request.Headers.Add("Authorization", api_key);
                    var response = await HttpClient.SendAsync(request);

                    response.EnsureSuccessStatusCode();

                    var m = await response.Content.ReadAsStringAsync();
                }

enter image description here

Solution 2: Try calling it like a browser :

                HttpClient httpClient = new HttpClient();
                string url = "https://gatewayt.whatever.com/chkt/request/request.php";
                string end_point = "/offers?";
               string  api_key = "key here";
                string page_query = "page_number=" + 10 + "&page_size=" + 20;
                string full_url = url + end_point + page_query;

                httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Accept", "text/html,application/xhtml+xml,application/xml");
                httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Accept-Encoding", "gzip, deflate");
                httpClient.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:19.0) Gecko/20100101 Firefox/19.0");
                httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Accept-Charset", "ISO-8859-1");
                httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Key", api_key);

                var response = await httpClient.GetAsync(url);
                response.EnsureSuccessStatusCode();
                await response.Content.ReadAsStringAsync();

source: Request like browser Link1 Link2 Link3

MD. RAKIB HASAN
  • 3,670
  • 4
  • 22
  • 35
  • Unfortunately I have tried this and still getting `403`. I have also tried to only introduce the `HttpClient` in my service class to ensure there is only one client being used, and still returning `403`. I have triple checked the API key being used and still `403`. Again, the client query is well formed before the call and looks identical to the call being made from GAS. What else could be causing the issue? – Dean Mar 16 '22 at 06:46
  • could you please share your working code of ```GoogleAppScript```?? – MD. RAKIB HASAN Mar 16 '22 at 07:16
  • I have added in `EDIT2` – Dean Mar 16 '22 at 07:51
  • I am not sure how `UrlFetchApp` deserialises the `params` that are passed - I tried to find this but couldn't find any resource for this as I was hoping it would give me more information around how to adjust my C# call. – Dean Mar 16 '22 at 08:19
  • @Dean Answer is edited please check..... – MD. RAKIB HASAN Mar 16 '22 at 09:08
  • Unfortunately, the request is well formed, i.e. the key is in the Authorization header when being sent - it is still returning `403` with that... – Dean Mar 16 '22 at 09:24
  • @Dean please try [Solution 2], this must be help you to solve this issue.. – MD. RAKIB HASAN Mar 16 '22 at 10:10