Skip to the content.

curl Authentication Errors in C# (401 and 403)

curl authentication errors in C# show up as HTTP 401 Unauthorized or 403 Forbidden responses when credentials are missing, wrong, or sent with the wrong scheme. CurlDotNet does not throw for these: the request completes and you get a CurlResult with IsSuccess == false and the StatusCode, so you decide whether to retry, refresh a token, or fail. The one authentication exception is CurlAuthErrorException (exit code 94), thrown when a Digest, NTLM or Negotiate exchange cannot be carried out at all.

How Authentication Failures Surface

using CurlDotNet;

var result = await Curl.ExecuteAsync("curl https://httpbin.org/basic-auth/user/passwd");

if (result.StatusCode == 401)
    Console.WriteLine($"Login required: {result.GetHeader("WWW-Authenticate")}");
else if (result.StatusCode == 403)
    Console.WriteLine("Authenticated, but not allowed to access this resource");

The WWW-Authenticate response header tells you which scheme the server expects (Basic, Digest, Bearer, …).

If you prefer an exception, add -f to the command (it throws CurlHttpReturnedErrorException, exit code 22) or call result.EnsureSuccess() (it throws CurlDotNet.Core.CurlHttpException with StatusCode, ResponseBody and ResponseHeaders).

When Authentication Throws

Exception CurlErrorCode Thrown when
CurlAuthErrorException 94 --digest, --ntlm, --negotiate or --anyauth (or the --proxy-* variants) cannot process the server’s WWW-Authenticate / the proxy’s Proxy-Authenticate challenge, for example because a parameter is malformed or repeated. Properties: Scheme (digest, ntlm, negotiate or anyauth) and Proxy
CurlException (plain) 100 The NTLM user name (with domain) is too long for the NTLM message
CurlAuthenticationException, CurlLoginDeniedException 67 Reserved: they exist in CurlDotNet.Exceptions but nothing throws them today

A wrong password is not an exception: the server answers 401 again and you get that result.

using CurlDotNet;
using CurlDotNet.Exceptions;

try
{
    var result = await Curl.ExecuteAsync("curl --digest -u user:passwd https://httpbin.org/digest-auth/auth/user/passwd");
    Console.WriteLine(result.StatusCode);   // 200, or 401 for wrong credentials
}
catch (CurlAuthErrorException ex)
{
    Console.WriteLine($"{ex.Scheme} negotiation failed{(ex.Proxy ? " with the proxy" : "")}: {ex.Message}");
}

Fix: try --basic (or --proxy-basic) if the server also offers Basic, and inspect the challenge with -v.

Sending Credentials

Basic Authentication (-u)

var result = await Curl.ExecuteAsync("curl -u user:passwd https://httpbin.org/basic-auth/user/passwd");
Console.WriteLine(result.StatusCode); // 200

Builder equivalent:

using CurlDotNet.Core;

var result = await CurlRequestBuilder
    .Get("https://httpbin.org/basic-auth/user/passwd")
    .WithBasicAuth("user", "passwd")
    .ExecuteAsync();

Digest, NTLM and Negotiate

Pick the scheme with the matching curl flag; --anyauth lets the server choose (preferring Digest, then NTLM, then Basic). The challenge and response are computed in-library in pure C#, like curl: a POST or PUT first sends a Content-Length: 0 probe, and no Basic header is sent up front when a challenge scheme is selected:

var digest = await Curl.ExecuteAsync("curl --digest -u user:passwd https://httpbin.org/digest-auth/auth/user/passwd");
var any = await Curl.ExecuteAsync("curl --anyauth -u user:passwd https://httpbin.org/digest-auth/auth/user/passwd");

Bearer Tokens

var token = "eyJhbGc...";
var result = await Curl.ExecuteAsync($"curl -H 'Authorization: Bearer {token}' https://httpbin.org/bearer");

Builder: CurlRequestBuilder.Get(url).WithBearerToken(token).

API Keys in a Header

var apiKey = Environment.GetEnvironmentVariable("MY_API_KEY");
var result = await Curl.ExecuteAsync($"curl -H 'X-API-Key: {apiKey}' https://httpbin.org/headers");

Retrying After a 401

A common pattern when a token has expired:

async Task<CurlResult> CallWithTokenRefresh(string url)
{
    var result = await Curl.ExecuteAsync($"curl -H 'Authorization: Bearer {_token}' {url}");
    if (result.StatusCode != 401)
        return result;

    _token = await RefreshTokenAsync();   // your own token endpoint call
    return await Curl.ExecuteAsync($"curl -H 'Authorization: Bearer {_token}' {url}");
}

(CurlResult is in CurlDotNet.Core.)

Checklist for Common Issues

Invalid Credentials

Credentials Lost on a Redirect

With -L, the Authorization header (from -u, --oauth2-bearer, -H 'Authorization: ...' or user:pass@ in the URL) is not sent to a different host, as in curl. If the API redirects to another host and then answers 401, add --location-trusted when you trust the target.

Wrong Authentication Scheme

403 After a Successful Login

Seeing What Is Sent

Use -v to confirm the Authorization header is present in the outgoing request; the request headers are written at the start of result.Body:

var result = await Curl.ExecuteAsync("curl -v -u user:passwd https://httpbin.org/basic-auth/user/passwd");
Console.WriteLine(result.Body);