HttpClient, .NET, Access denied error exception. But works with cURL

Indinfer

Active Member
Mar 25, 2018
26
3
53
Baltimore, Maryland
cPanel Access Level
Website Owner
Here is my F# code that raises the exception of Access denied:


open System
open System.Net.Http
open System.Net.Http.Headers
open System.Text

open System.Diagnostics
[INDENT][/INDENT]
// global constants
let username = "indinferaem"
let token = "IAQY7WHZT9JUJ7U5CJ926N0W4LWO8N0F"
let domain = "assigned-email-manager.com"
let cPanelFunction = "list_pops"

let getEboxList (): unit = // function definition
[INDENT] let authHeader = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{token}"))[/INDENT]
[INDENT] let client = new HttpClient()[/INDENT]
[INDENT] client.DefaultRequestHeaders.Authorization <- AuthenticationHeaderValue("Basic", authHeader)[/INDENT]
[INDENT] let response = client.GetStringAsync($"https://{domain}:2083/execute/Email/{cPanelFunction}").Result // <<<<< Exception on this line[/INDENT]
[INDENT] Debug.WriteLine(response)[/INDENT]
[INDENT] ()[/INDENT]



Note: the periods on the left of some lines are to make the spacing show in the post. Indentation is important in F#.
==================================================

I verified that I am using the correct username, token, and domain by using cURL:
THIS WORKS:
cURL --silent --header "Authorization: cpanel indinferaem:IAQY7WHZT9JUJ7U5CJ926N0W4LWO8N0F" "https://assigned-email-manager.com:2083/execute/Email/list_pops"

==================================================

I use the website, assigned-email-manager.com, as a sandbox. Therefore, this post does not contain any secret or private information. At the time of this posting, the token is valid. Later the token will be deleted.

F# uses the same HttpClient that C# uses from .NET. I don't know what cURL does that makes the request work and what HttpClient does that makes the request fail. There must be something wrong with the way I am using HttpClient. But what?
 
Last edited:

cPanelThomas

Developer
Feb 16, 2023
38
29
93
cPanel
cPanel Access Level
Root Administrator
Well, the problem with your Authorization: header appears to be that you are passing Basic as the method instead of cpanel if I'm reading your code correctly. Additionally, you are base64 encoding the user:token string, which is entirely unnecessary in this context. Fix that and it will probably get you going.

Probably you want something more like the following:
Code:
    ...
    let authHeader = $"{username}:{token}"
    let client = new HttpClient()
    client.DefaultRequestHeaders.Authorization <- AuthenticationHeaderValue("cpanel", authHeader)
    ...
That said, you probably should revoke that API token and regenerate something else while you are at it, as posting an API token on a public forum probably isn't the best idea.

Hope this helps!
 
  • Like
Reactions: cPRex

Indinfer

Active Member
Mar 25, 2018
26
3
53
Baltimore, Maryland
cPanel Access Level
Website Owner
cPRex,

Thank you. Your answer is exactly on target. Changing to
AuthenticationHeaderValue("cpanel", authHeader)
Fixed the problem.

I agree with you about the token and have revoked it. Although I am currently using assigned-email-manager.com as a sandbox, someone with, shall we say, impure motives could, for example, wreak havoc and make it look like I am doing it.

Here is the final code that works in case it helps someone else who is perhaps working in F#. Someone trying this code will have to replace the token with their own. And they will have to replace the domain name and username with those valid in their environment.




open System.Net.Http
open System.Net.Http.Headers
open System.IO

open System.Text

open System.Diagnostics


let getMailBoxes (domain: string) (username: string) (token: string): Async<string> =
[INDENT] async {[/INDENT]
[INDENT=2] // Create a URL for the cPanel API endpoint that lists the mailboxes.[/INDENT]
[INDENT=2] let url = $"https://{domain}:2083/execute/Email/list_pops"[/INDENT]
[INDENT=2][/INDENT]
[INDENT=2] // Create a new HttpClient object.[/INDENT]
[INDENT=2] use httpClient = new HttpClient()[/INDENT]
[INDENT=2][/INDENT]
[INDENT=2] // Set the Authorization header to the value of cpanel:{username}:{token}.[/INDENT]
[INDENT=2] httpClient.DefaultRequestHeaders.Authorization <- AuthenticationHeaderValue("cpanel", $"{username}:{token}")[/INDENT]
[INDENT=2] [/INDENT]
[INDENT=2] // Make a request to the cPanel API endpoint.[/INDENT]
[INDENT=2] // The GetAsync method is an asynchronous method returning a Task object. [/INDENT]
[INDENT=2] let! response = httpClient.GetAsync(url) |> Async.AwaitTask[/INDENT]
[INDENT=2][/INDENT]
[INDENT=2] // Checks the status code. 200 means success. But ignore the result. ???[/INDENT]
[INDENT=2] response.EnsureSuccessStatusCode() |> ignore[/INDENT]
[INDENT=2][/INDENT]
[INDENT=2] // Read the response body as a stream.[/INDENT]
[INDENT=2] let! stream = response.Content.ReadAsStreamAsync() |> Async.AwaitTask[/INDENT]
[INDENT=2][/INDENT]
[INDENT=2] // Create a new StreamReader object.[/INDENT]
[INDENT=2] use reader = new StreamReader(stream, Encoding.UTF8)[/INDENT]
[INDENT=2][/INDENT]
[INDENT=2] // Read the contents of the stream as a string.[/INDENT]
[INDENT=2] let! content = reader.ReadToEndAsync() |> Async.AwaitTask[/INDENT]
[INDENT=2] return content[/INDENT]
[INDENT=2] }[/INDENT]




let domain = "assigned-email-manager.com"
let username = "indinferaem"
let token = "IAQY7WHZT9JUJ7U5CJ926N0W4LWO8N0F"


let getEboxList (): unit =
[INDENT] let result = getMailBoxes domain username token |> Async.RunSynchronously[/INDENT]
[INDENT] Debug.WriteLine(result)[/INDENT]
[INDENT] ()[/INDENT]
 
  • Like
Reactions: cPRex