What is NTLM/Authenticate/Negotiate web authentication - c#

I understand basic and digest authentication. But I've searched a lot and I'm struggling with NTLM, Authenticate, & Negotiate.
I think, correct me if I am wrong, that NTLM & Authenticate are two terms for the same protocol.
And negotiate is trying first NTLM, then falling back to digest, then falling back to basic to connect.
Is that correct? And if so where is a good example of how to connect in C# both for NTLM only and for negotiate.
I have two use cases. The first is I need to pull down a single file. So make a request, get an XML file as the response, read it down, done.
The second is querying OData so hundreds to thousands of web requests, each of which will provide JSON (or XML) as the response.

Microsoft Negotiate is a security support provider (SSP) that acts as
an application layer between Security Support Provider Interface
(SSPI) and the other SSPs. When an application calls into SSPI to log
on to a network, it can specify an SSP to process the request. If the
application specifies Negotiate, Negotiate analyzes the request and
picks the best SSP to handle the request based on customer-configured
security policy.
https://learn.microsoft.com/en-us/windows/desktop/secauthn/microsoft-negotiate
As given in the article Negotiate does not fall back to digest. In a way Negotiate is like Kerberos but with a default backup of NTLM
Currently, the Negotiate security package selects between Kerberos and
NTLM. Negotiate selects Kerberos unless it cannot be used by one of
the systems involved in the authentication or the calling application
did not provide sufficient information to use Kerberos.
Windows Challenge/Response (NTLM) is the authentication protocol used
on networks that include systems running the Windows operating system
and on stand-alone systems.
Authenticate is just an internal method, not sure why you are getting confused with it and the protocols, a good look at the internals is here: https://blogs.msdn.microsoft.com/dsnotes/2015/12/30/negotiate-vs-ntlm/
The way to look at this is:
Microsoft came up initially with a way to authenticate on Windows servers/machine which they called NTLM , this used a request/response (sometimes called challenge) method.
Subsequently they came up with a new protocol called Kerberos which was adopted.
To make sure that existing application all function properly with old/new we have a new way to authenticate called Negotiate, which tried Kerberos and if that is not available goes for NTLM.
Edit 1 : Applying these authentication mechanisms for the Web was formalized in RFC 4559.
Edit 2 : NTLM authenticates one connection, not a request, while other authentication mechanisms usually authenticate one request. On the first use case this should not change so much, but for the second use case this makes sense to try NTLM while keeping one single connection (by using the HTTP Keep-Alive, and sending the credentials only once in the first request). There's maybe a performance difference. Keep us updated with your results.
A sample WebRequest code taken from Microsoft docs, you can replace the Webrequest with HttpWebRequest.
// Create a request for the URL.
WebRequest request = WebRequest.Create(
"http://www.contoso.com/default.html");
// If required by the server, set the credentials.
request.Credentials = CredentialCache.DefaultCredentials;
// Get the response.
WebResponse response = request.GetResponse();
// Display the status.
Console.WriteLine (((HttpWebResponse)response).StatusDescription);
// Get the stream containing content returned by the server.
Stream dataStream = response.GetResponseStream();
// Open the stream using a StreamReader for easy access.
StreamReader reader = new StreamReader(dataStream);
// Read the content.
string responseFromServer = reader.ReadToEnd();
// Display the content.
Console.WriteLine(responseFromServer);
// Clean up the streams and the response.
reader.Close();
response.Close();

Related

How to Consume WebApi WebService that Requires Redirect for Login

We have a desktop application that requires authentication with a server in order to operate. This application prepares and sends a query to a webservice, the user is prompted from this webservice to log in and the webservice returns an XML document with application subscription information (Software-as-a-Service Subscription License).
I've created a WebApi webservice that does the following:
Accept incoming request at /api/client?[MACHINEINFOINQUERYSTRING]
Redirect to external authentication provider (think GoogleId or
similar)
Authentication provider sends information back to
/api/subscription/[AUTHENTICATIONID]
The /api/subscription endpoint returns an XML document after pulling info from the servers (or including appropriate error message).
This webservice works and the XML document can be viewed in the browser. I've created a website with a default.aspx to test this, automatically redirecting to the /api/client and it does display this XML document in the browser.
The desktop application properly makes the initial call, redirects through an embedded browser to the login page, and receives an XML, but this XML cannot be parsed. The application team simply gets a "download" option for the XML but cannot capture the response for stuffing into an XmlDocument object. I've attempted to create an example application to instruct the app team, but have had no success.
Questions:
Do I have this architecture fundamentally wrong or do we simply not know how to consume the response properly?
How do I capture and consume the XML that is successfully returned?
As an example of what I've tried:
string requestString = string.Format("http://[server]/api/client?{0}", HttpUtility.HtmlEncode(queryString));
Response.Redirect(requestString);
This works in the browser, displays the login page, allows for input, redirects to the subscription endpoint which then prepares and delivers the XML to the browser. Unfortunately, this is unusable by a consumer.
HttpWebRequest request = WebRequest.Create(requestString) as HttpWebRequest;
request.AllowAutoRedirect = true;
request.MaximumAutomaticRedirections = 20;
request.AuthenticationLevel = System.Net.Security.AuthenticationLevel.None;
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
This doesn't work. The response.ResponseUri has the properly formatted address of the OAuth service (step 2 above). It does not display the login page to the user even though this is all initiated through a browser.
I've also tried using a WebRequest POST, HttpClient PostAsync and several other methods, but:
The response URL is simply the location of the login page. If I string together 3 WebRequest/WebResponse pairs, it fails because the user isn't being properly authenticated at the first request / initial redirect.
What does work in my default.aspx:
I haven't found an example online for my specific needs, but the pattern must exist in practice as plenty of websites utilize OAuth style logins. I've utilized webservices (like OData endpoints) that also require logins, so this pattern must exist for webservices too. I do send back a properly formatted XML document. We just don't know how to capture and consume that document.
Any examples of a similar architecture would be highly appreciated! Or pointing me in the right direction.
Edit ---
I'm thinking that somehow the request.GetResponse() isn't really allowing for redirects and/or since it's an HttpWebRequest, there's no way it will allow for user input.
What's the proper way to make this call and consume the XML from another application? the XML is delivered properly in a browser window (with Response.Redirect) but no login window opens using an HttpWebRequest.
The answer is: This architecture is fundamentally wrong.
OAuth architecture leaves the authorization to the client, which then sends an authorization token to all subsequent services that it requires. The services are simple endpoints and do not contain any authentication logic, (although the service itself is allowed to validate the authentication token with the OAuth server).
The proper sequence of events for this answer should be:
1) Desktop Application makes OAuth authentication request to authentication server.
2) A successful response includes an authorization token which contains identity information, permissions, validity period, etc.
3) The desktop app then requests information from the WebApi service, sending in the request that token.
4) WebApi takes this authentication token, validates it (in my case against a certificate) and may even query the OAuth server again to ensure that the token is still valid.
5) If valid, the web service gathers the data and sends it back to the server.
My problem was I was expecting the subscription service itself to be able to open a web browser, prompt for a login, and then continue the request to another endpoint (2 redirects after initial request). I was in effect, breaking both the WebApi and OAuth 2 designs. Although it worked from the browser, it was not consumable from an application.
After redesigning to this simpler pattern, my web service is now consumable.

How to identify, what type of authentication from URL?

I am writing a C# application to get some information from URL.
The Url's are of Web applications hosted in IIS in our local intranet environment.
The web applications are developed using ASP.NET (C#).
In IIS these are configured as either Forms or Windows authentication.
Is there a way to identify what type of authentication is the corresponding web application urls ?
This is what i got so far !
using System;
using System.IO;
using System.Net;
public class Program
{
public static void Main()
{
var url = "http://www.contoso.com/default.html";
WebRequest request = WebRequest.Create(url);
HttpWebResponse response = (HttpWebResponse)request.GetResponse ();
//he i need to know what type of authentication the url
Console.WriteLine (response.StatusDescription);
Stream dataStream = response.GetResponseStream();
StreamReader reader = new StreamReader (dataStream);
string responseFromServer = reader.ReadToEnd();
reader.Close ();
dataStream.Close ();
response.Close ();
}
}
Demo
Guessing by url is not accurate. But you can use the header information to make an educated guess. Windows and basic authentication give you enough information. Hence check for those first.
Below is the header information to determine the authentication type. Check for basic and windows first. You can default to forms if not basic or windows and there is a redirect. And if no redirect, there is no authentication.
Windows Authentication
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
Basic Authentication
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="test.local"
Forms Authentication
HTTP/1.1 302 Found
Location: /Account/Login?ReturnUrl=%2f
No, not based on the URL, unless you specifically add something to the URL to indicate it. But you would know if you did that.
Since you own the sites, you should know ahead of time what type of authentication they use, without needing to look at the URL, right? Or you could programatically connect to the server and check the IIS settings. You can also detect authentication type by inspecting the HTTP headers in the response from the server, but that's outside the scope of your question.
If your web application is written correctly then when a protected resource is requested it should respond with a 401 status and a WWW-Authentication header containing the authentication challenge. Decoding the authentication challenge will show the authentication types supported (Basic, Digest, NTLM etc). Read more at Understanding HTTP Authentication.
The WebRequest supports this challenges and authenticates itself using the Credential configured for that challenge.
IF your application uses forms and cookies for authentication then WebRequest will not be able to authenticate itself. You will have to add support, using for instance WebClient instead.
See How do I log into a site with WebClient?

Authenticating App with OAuth from C#

I am writing an app that will talk with Salesforce. Salesforce provides access to APIs via OAuth. I've been attempting to go through the OAuth authentication process described here. Currently, I'm attempting to authorize my app. I have the following code.
// Ask Salesforce for a request token
var request = (HttpWebRequest)(WebRequest.Create(String.Format("https://login.salesforce.com/services/oauth2/authorize?response_type=code&client_id={0}&redirect_uri=http://localhost:5004/home/AuthCallback", CONSUMER_KEY)));
request.Method = "POST";
request.ContentType = "application/json";
// Retrieve the request token from the response
var response = (HttpWebResponse)(request.GetResponse());
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
string accessCodeData = accessCodeReader.ReadToEnd();
}
This code is triggered when a user clicks a button in my ASP.NET MVC view. When executed, this code calls to Salesforce. I see a request in fiddler. The request header looks like this:
POST /services/oauth2/authorize?response_type=code&client_id={consumerKey}&redirect_uri=http://localhost:5004/home/AuthCallback HTTP/1.1
I am in fact passing my consumer key, I'm just removing it from the example. Regardless, this request returns a 302, with a body size of 0. I might be misunderstanding something. However, I was expecting to get a request token. I was then going to use the request token to get the access token.
What am I doing wrong?
You are misusing the API.
Take a closer look at the sequence diagram at their page (under Obtaining an Access Token): in the auhorization_code flow you are supposed to redirect the browser to their page so that the user sees the login page, provides his/her credentials and you get the token back. Instead, you are trying to POST there using a web request from your server.
This particular flow belongs then to the passive flows group, this group is intended to be used in browser apps, your server redirects the browser to their server and you basically get the response to the uri passed in the redirect_uri parameter and this should point back to your application at your server.
There are other flows, of them one is suited for non-browser apps, it is called resource owner password flow. In this flow it is your application that hosts the login UI and you send the username/password to the authorization server and you get the token back. It is to be read in their docs however whether this flow is supported.
Read more here: http://aaronparecki.com/articles/2012/07/29/1/oauth2-simplified
Take a look how I handle the OAuth2 flow to Google, using the DotNetOpenAuth library. This is a direct solution, applying it to any other provider should be straightforward.
http://www.wiktorzychla.com/2014/11/simple-oauth2-federated-authentication.html

Why would my REST service .NET clients send every request without authentication headers and then retry it with authentication header?

We happen to run a REST web service with API requiring that clients use Basic authentication. We crafted a set of neat samples in various languages showing how to interface with our service. Now I'm reviewing IIS logs of the service and see that the following pattern happens quite often:
a request comes, gets rejected with HTTP code 401
the same request is resent and succeeds
which looks like the first request is sent without Authorization headers and then the second one is sent with the right headers and succeeds. Most of the time the log record contains "user-agent" which is the same string we planted into our .NET sample.
So I assume the problem is with .NET programs only. The problem is not reproduced with our sample code so I assume the users somehow modified the code or wrote their own from scratch.
We tried contacting the users but apparently they don't want to invest time into research. So it'd be nice to find what the most likely scenario is which leads to this behavior of .NET programs.
Why would they do this? Why would they not attach the headers on the first attempt?
This is the default behavior of HttpClient and HttpWebRequest classes which is exposed the following way.
Note: Below text explains suboptimal behavior causing the problem described in the question. Most likely you should not write your code like this. Instead scroll below to the corrected code
In both cases, instantiate a NetworkCredenatial object and set the username and password in there
var credentials = new NetworkCredential( username, password );
If you use HttpWebRequest - set .Credentials property:
webRequest.Credentials = credentials;
If you use HttpClient - pass the credentials object into HttpClientHandler (altered code from here):
var client = new HttpClient(new HttpClientHandler() { Credentials = credentials })
Then run Fiddler and start the request. You will see the following:
the request is sent without Authorization header
the service replies with HTTP 401 and WWW-Authenticate: Basic realm="UrRealmHere"
the request is resent with proper Authorization header (and succeeds)
This behavior is explained here - the client doesn't know in advance that the service requires Basic and tries to negotiate the authentication protocol (and if the service requires Digest sending Basic headers in open is useless and can compromise the client).
Note: Here suboptimal behavior explanation ends and better approach is explained. Most likely you should use code from below instead of code from above.
For cases when it's known that the service requires Basic that extra request can be eliminated the following way:
Don't set .Credentials, instead add the headers manually using code from here. Encode the username and password:
var encoded = Convert.ToBase64String( Encoding.ASCII.GetBytes(
String.Format( "{0}:{1}", username, password ) ) );
When using HttpWebRequest add it to the headers:
request.Headers.Add( "Authorization", "Basic " + encoded );
and when using HttpClient add it to default headers:
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue( "Basic", encoded );
When you do that the request is sent with the right authorization headers every time. Note that you should not set .Credentials, otherwise if the username or password is wrong the same request will be sent twice both time with the wrong credentials and both times of course yielding HTTP 401.

HTTPS Request to HTTP Drops Headers?

This is more for curiosity as I'm failing to find any answers or documentation for this phenomenon, but here's the scenario:
There are 2 services/applications, both hosted on IIS 7. Service 1 receives an HTTPS request from an external source (browser, fiddler, etc.) and to validate the request it needs to call service 2, so service 1 makes its own, new, separate call over HTTP to service 2. This call has an Authorization header added to the request object. When service 2 receives this call, the authentication header is gone, as if stripped out. Thus the authentication fails, this returns to service 1 which then rejects the external call.
Does anyone have an explanation why this header, and some others from what I've seen in testing, doesn't make it through with the HTTP call? Is this a behavior of IIS, or ASP.NET, or something? If the call to service 2 was HTTPS then the headers make it through fine. I'm generating the request like so:
string uriendpoint = "http://service.test.com/testService.svc/authtest";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uriendpoint);
request.Credentials = CredentialCache.DefaultCredentials;
var authField = MD5Hash("test:test!!2013");
request.Headers.Add(HttpRequestHeader.Authorization, authField.ToString());
request.Method = WebRequestMethods.Http.Get;
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Most likely the "service 2" have code similar to "If incoming request is HTTP ignore authorization headers". It is very reasonable behavior as HTTP traffic can be very easily sniffed and replayed - so honest servers block callers from potentially unsecure behavior.
A co-worker of mine came across the root cause of this behavior being IIS's "URL Rewrite" module. We had it setup to do a permanent redirect of http requests to https, and this redirect is where the headers get dropped. It's a bit odd that IIS does this, but I guess I'll just try something else to get around this problem.

Categories

Resources