How to 'share' NTLM authentication across multiple HttpWebRequests? - c#

My C# app hits a web server that uses NTLM authentication.
I find that each request made to the server (using a new HttpWebRequest) is individually authenticated. In other words, every request results in a 401 response, after which an NTLM handshaking conversation occurs before I then get the actual response.
e.g.:
First GET request:
-> GET xyz
<- 401 error (WWW-Authenticate:NTLM)
-> GET xyz (Authorization:NTLM base64stuff)
<- 401 error (WWW-Authenticate:NTLM base64stuff)
-> GET xyz (Authorization: base64stuff)
<- 200
Subsequent requests:
-> GET xyz (Authorization:NTLM base64stuff)
<- 401 error (WWW-Authenticate:NTLM) //can this request be avoided?
-> GET xyz (Authorization: base64stuff)
<- 200
(initially, with PreAuthenticate set to false, the subsequent requests looked like the first request - i.e. three underlying requests per 'request')
Is there a way to 'share' the authentication performed on the first request to the server with subsequent HttpWebRequests?
I thought perhaps the UnsafeAuthenticatedConnectionSharing property would allow me to do this, but setting it to true for all HttpWebRequest objects used in the app has no effect.
However if I set PreAuthenticate to true, one less 401 response happens for each request after the first one.

Last request sent after NTLM is performed (the one that results in a 200 response) contains an auth header that tells the server that you have the correct credentials.
I'm not sure if the client class has the feature to keep this by its own, but if you find some way to retain this header and add it to your subsequent requests it should work fine.
Update: NTLM authenticates a connection, so you need to keep your connection open using Keep-Alive header. The client class should provide some settings for this.

Maybe it is a bit too late for it, but you have to set the UnsafeAuthenticatedConnectionSharing property to true in the WebRequestHandler (it extends HttpClientHandler).
This way connections are kept alive by allowing the HttpClient to "share" the authentication among other requests, allowing at the same time the connections to be kept alive (you can't do this manually even by setting the header yourself). Bear in mind that you should also have the appropriate persistent authorization in the server, either with authPersistNonNTLM for Kerberos or with authPersistSingleRequest for NTLM.

Related

How to consume a socket.io WebSocket API in C#

I need to consume a third-party WebSocket API in .NET Core and C#; the WebSocket server is implemented using socket.io (using protocol version 0.9), and I am having a hard time understanding how socket.io works... besides that the API requires SSL.
I found out that the HTTP handshake must be initiated via a certain path, which is...
socket.io/1/?t=...
...whereby the value of the parameter t is a Unix-timestamp (in seconds). The service replies with a session-key, timeout information, and a list of supported transport protocols. Due to simplicity, this first request is made via HttpClient and does not involve any additional headers.
Next, another HTTP request is required, which should result in an HTTP 101 Switching Protocol response. I specified the following headers in accordance to the previous request...
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key: ...
Sec-WebSocket-Version: 13
...whereby the value of the Key-header is a Base64-encoded GUID-value that the server will use to calculate the Sec-WebSocket-Accept header value. I also precalculate the expected Sec-WebSocket-Accept header value, for validation...
I tried to make that request using HttpClient as well, but that does not seem to work... I actually don´t understand why, because I expect an HTTP response. I also tried to make the request using TcpClient by sending a manually prepared GET request over a SslStream, which accepts the remote certificate as expected. Sending data seems to work, but there´s no response data... the Read-method returns zero.
What do I miss here? Do I need to setup a listener for the WebSocket connection as well, and if yes how? I don´t want to implement a feature complete socket.io client, I´d just like to keep it as simple as possible to catch some events...
The best way of debugging these issues is to use a sniffer like wireshark or fiddler. Often connect using an IE and compare IE results with my application and modify my app so it works like the IE. Using WebClient instead of HttpClient will also work better because the WebClient does more automatically than the HttpClient.
A web connection uses the header of the client and the headers in the server webpage to negotiate a connection mode. Adding additional headers to you client will change the connection mode. Cookies are also used to select the connection mode. Cookies are the results of previous connection to the same server which shortens the negotiations and stores info from previous connection so less data has to be downloaded from server. The server remembers the cookies. Cookies have a timeout and is kept until timeout expires. The IE history in your client has a list of IP addresses and Net automatically sends the cookies associated with the server IP.
If a bad connection is made to the server the cookies is also bad so the only was of connection is to remove the cookie. Usually I go into the IE and delete cookies manually in the IE history.
To check if a response is good the server returns a status. A completed response contains a status 200 DONE. You can get status which are errors. You can also get a 100 Continue which means you need to send another request to get the rest of the webpage.
Http has 1.0 (stream mode) and 1.1 (chunk mode). Net library doesn't work with chunk. Chunk requires client to send message to get next chunk and I have not found a way in Net to send the next chunk message. So if a server responds with a 1.1 then you have to add to your client headers to use 1.0 only.
Http uses TCP as the transport layer. So in a sniffer you will see TCP and HTTP. Usually you can filter sniffer just to return Http and look at header for debugging. Occasionally TCP disconnects and then you have to look at TCP to find why the disconnect occurs.

Check url with NTLM on server side

I need to check if url exists and can be reached. In order to do it I send Get request and handle the status:
var httpClient = new HttpClient();
var response = httpClient.GetAsync(new Uri(pageUrl));
isPageAccessible = response.Result.StatusCode == HttpStatusCode.OK;
However, the server uses NTLM for authentication. As I found it here, there are several steps (requests) before I get success OK status. For the first request I get 401 Unauthorized status and can't go to further steps.
All in all, how can I check url on the server with NTML upon completion of all requests?
If you are accessing an authenticated server, you should provide credentials. Credentials of running process for NTLM can be provided with HttpClient as below:
var handler = new HttpClientHandler {
Credentials = System.Net.CredentialCache.DefaultCredentials
};
var httpClient = new HttpClient(handler);
var response = httpClient.GetAsync(new Uri(pageUrl));
You're setting yourself up for failure since there are dozens of reasons why a request may not return a 200 OK response. One may be that the response has no content 204 No Content. Another may be that the request only accepts POST or PUT requests. Another, as you've discovered, may be that it has an authentication system in front of it 401 Not Authorized. Another may be just that the response is a redirect 301 Moved Permanently or 302 Found. Or it could be behind a proxy 305, 306, etc.
The only way you can determine if a URL really exists is to request that the other end prove it. Google does it, Facebook does it, Pinterest does it, etc. The way they do it is they ask the sender to set an MX record in their DNS or a meta tag on their index.html with a custom token they generate. If the token exists, then they're who they say they are.
Anything else is unreliable.

Which HTTP Status to return when external authentication fails?

In my ASP.NET MVC web application, I have an external single-sign-on which is used to authenticate the user centrally in the enterprise. The SSO is supposed to return a "packet" of stuff that identifies the user uniquely, which would then be used to pass to local ASP.NET Owin Cookie Authentication (or alternatively Forms Authentication, or something like that). If for some reason the SSO-provided "packet" of stuff doesn't contained minimum required info, I'd like to handle it in the local ASP.NET level appropriately. It's a pretty severe error and not necessarily something that would commonly occur. So I thought I would do something like this:
public class AuthController() : Controller
{
public ActionResult Login(string returnUrl)
{
// Process external single-sign-on authentication
bool isSuccess = ProcessExternalAuth();
if (!success)
// return appropriate HTTP status code
else
// continue with login
}
}
But I'm not sure which HTTP status code to return. Would something like this be appropriate?
return new HttpStatusCodeResult(
HttpStatusCode.BadGateway, "SSO authentication failed.");
According to List of HTTP Status Codes, one of the 500 errors would probably be most appropriate:
Response status codes beginning with the digit "5" indicate cases in which the server is aware that it has encountered an error or is otherwise incapable of performing the request...
Error code 401 and error code 403 are typically used for authentication related errors. These however have some standards that go along with them that specify when and how they should be used. In your instance neither of these would quite fit. For example, a 403 is described as (http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html):
403 Forbidden
The server understood the request, but is refusing to fulfill it. Authorization will not help and the request SHOULD NOT be repeated. If the request method was not HEAD and the server wishes to make public why the request has not been fulfilled, it SHOULD describe the reason for the refusal in the entity. If the server does not wish to make this information available to the client, the status code 404 (Not Found) can be used instead.
A good alternative would be to return code 503 (service unavailable).
503 Service Unavailable
The server is currently unable to handle the request due to a temporary overloading or maintenance of the server. The implication is that this is a temporary condition which will be alleviated after some delay.
Errors authenticating clients should be under 401:
401 Unauthorized (RFC 7235)
Similar to 403 Forbidden, but specifically for use when authentication is required and has failed or has not yet been provided. The response must include a WWW-Authenticate header field containing a challenge applicable to the requested resource. See Basic access authentication and Digest access authentication.[36] 401 semantically means "unauthenticated", i.e. the user does not have the necessary credentials.
Note: Some sites issue HTTP 401 when an IP address is banned from the website (usually the website domain) and that specific address is refused permission to access a website.
https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_Client_Error

OnVif integration error - unauthorized with client authentication scheme 'Digest'

Using this code to connect to OnVif service on my IP Camera in C# .Net 4.5.
Very next to that code, I am trying to get Device information as follows:
string str = client.GetDeviceInformation(out model, out firmware, out serial, out hardwareId);
And encouters this error:
An exception of type 'System.ServiceModel.Security.MessageSecurityException' occurred in mscorlib.dll but was not handled in user code
Additional information: The HTTP request is unauthorized with client authentication scheme 'Digest'. The authentication header received from the server was 'Basic realm="CBBA"'.
I tried this information about Basic realm. By adding an Http header WWW-Authenticate to the request with value Basic realm="Our Site". But no luck.
What the heck is that? How to fix it?
You are probably failing to handle the Digest Authentication. To handle things correctly, you should send a request to a not PRE_AUTH ONVIF method without using authentication, neither digest nor ws-usernameToken.
You can get one of the two following answers:
you get a 500 response with env:Sender ter:NoAuthorized which means that the camera does not support HTTP digest (which in fact, for profile S devices is not mandatory). In this case you have to use only WS-UsernameToken
you get a 401 response with the WWW-Authenticate header from the device. In this case you should follow the RFC 2617, evaluate all the necessary elements and resend your requests with the Authorization header.
Remember that an HTTP digest challange is always started by the camera, you can't just add it to the first request.
Check §5.12 of the Core specification, at least to know has a camera handles authentication.
I had a relevant issue - I had to authenticate with ONVIF cameras for which I did not know the authentication HTTP authentication modes (Basic/Digest). In my answer you can see how to properly iterate through all the HTTP authentication schemes or you can just take the code for Digest, hope it helps.

Get Host of HttpResponseMessage in Windows Store App

I have a Windows Store App (C#) where I am sending a HttpRequest and I want to check if the response I am getting is from a Captive/Limited Access Network or from the actual host specified in the HttpRequest.
So lets say I am sending a request to www.serverA.com
I look at the response of that request and determine if it was success based on the status code.
Imagine the same scenario in a captive network(airport networks/starbucks where they redirect you to a login page):
I am sending a request to www.serverA.com
My request gets redirected
to www.serverB.com/AirPortLoginPage
I get back a response that the
AirportLoginPage loaded successfully with a 200 response
My code sees that as a success because of the 200 status code, but I wanted to know if my original request was successful
So, is there a way to determine the host of the server where the Response Message is coming from?
Two things which can solve your problem:
You can set HttpClientHandler.AllowAutoRedirect property to
false. But if any other code depends on this - you will need to
handle 3xx (redirect) manually.
You can check HttpResponseMessage.RequestMessage. In your example after you will send request to www.serverA.com - this property will have www.serverB.com/AirPortLoginPage

Categories