views:

802

answers:

1

Hi,

I have a website that uses Basic Authentication (username/password).

Why is the following code not working? When I run it the web application takes me to the login controller, whereas I'm expecting that it should already be authenticated given I'm populating the credentials. In other words I'm trying to confirm how, in .NET, I confirm my winforms HttpWebRequest so that it will automate the authentication process. I'm assumeing that NetworkCredential is the .net class that should do this? Or in .NET is there an expectation there is some manual two step process you have to implement yourself?

Here is the code:

    // Create a new webrequest to the mentioned URL.            
    var uri = new Uri("http://10.1.1.102:3000/download/sunset");
    var myWebRequest = (HttpWebRequest)WebRequest.Create(uri);            
    myWebRequest.PreAuthenticate=true;            
    var networkCredential=new NetworkCredential("test", "asdfasdf");                        
    myWebRequest.Credentials=networkCredential;
    var myWebResponse = (HttpWebResponse)myWebRequest.GetResponse();

    Console.Out.WriteLine("STATUS = " + myWebResponse.StatusCode);

    var stream = myWebResponse.GetResponseStream();
    var reader = new StreamReader(stream);
    string text_read = reader.ReadToEnd();
    Console.WriteLine(text_read);
    DisplayHtml(text_read);
    reader.Close();
    stream.Close();
    myWebResponse.Close();

Thanks

+2  A: 

WebRequest does not send credential unless challenged by the site. The site should respond first 401 'Authorization Required' with an WWW-Authenticate header, the WebRequest will respond to the challenge with credentials and the service should respond with the 200 http content.

Shounds like the site does not implement Basic auth properly (the spec says it shoudl challenge first, to pass in the realm) which is a very common behavior. You can add the Basic authentication manually to the WebReauest.Headers colelction, which is what most developers end doing anyway.

See http://stackoverflow.com/questions/1702426/httpwebrequest-not-passing-credentials/1702529#1702529

Remus Rusanu
Btw, WebRequest will send the credentials in the first request for Basic authentication if PreAuthenticate is used
Gonzalo
WebRequest will send credential if a CredentialsCache is used on **subsequent** calls when PreAuthenticate is true. Spec is pretty clear: "With the exception of the first request, the PreAuthenticate property indicates whether to send authentication information with subsequent requests without waiting to be challenged by the server". http://msdn.microsoft.com/en-us/library/system.net.webrequest.preauthenticate.aspx
Remus Rusanu
So do you mean I should do an initial hit on the website, using PreAuthenticate, and then really start from the next request on? Make sense (I'm not at my Dev PC at the moment)
Greg
BTW - I notice that the web application HTTP logs actually show status response 200's for both (a) when I use and login directly via a web browser, and (b) for the case I tested with my .NET winforms client when I posted this. When using the site via my browser it works fine however (i.e. takes me if not logged in to the login page etc).
Greg
The first hit should be transparent for your WbRequest. When you call GetReponseStream() it does 1) a first HTTP GET w/o authentication headers, 2) the web site *should* respond 401, then 3) the WebRequest makes another GET with authentication headers and finally 4) web site responds 200 with content, 5) GetReponseStream() function completes. The usual problem (you should validate) is that the site does *not* respond with 401 at step 2).
Remus Rusanu
oh - so it sounds like to need to make sure my Ruby on Rails web application is correctly offering up a 401 response for an initial client connect - I should be able to test this with my web browser and monitor the HTTP request/responses for testing purposes here correct? That is a web app should be offering up this 401 to a client irrespective of whether it's a browser or a non-browser client? thanks again
Greg
If that is the case the work around (short of changing the *site* code) is to force the Authorization header in the WebRequest by populating directly the myWebRequest.Headers.Add("Authorization", ...), see the link in my post. This in effect shortcircuits the whole NetworkCredentials/PreAuthenticate.
Remus Rusanu
If your RR app works correctly when you visit an URL the browser should display the Login credentials dialog and *only* if you hit Cancel should you be redirected to the login page. If the browser takes you *directly* to the login page it means the RR app does not implement Basic auth correctly.
Remus Rusanu
@Remus Rusanu: he's not using a CredentialCache. And, really, PreAuthenticate + NetworkCredential sends the Authorization header in the first request.
Gonzalo
Wether you change the RR app or change the WebRequest to force the Authorization header is your call. Making the RR app respond 401 if the request has no Authorization header and have the browser display the Login dialog box may not be what you desire. You can differentiate based on request header (true browser vs. test client) but then the obvious question will be 'what is you're actually testing?'. Ideally your test should behave just like the browser user experience.
Remus Rusanu
excellent info guys thanks - I now realize yes my web app is not implementing the true BASIC auth, but rather the normal Ruby on Rails approach where you take people to a login page (not sure if this is a Rails special, or whether it aligns with the http spec under "forms authentication"?). So noting this, sounds like I should try Gonzalo's PreAuthenticate + NetworkCredential first then? So does approach (i.e. PreAuthenticate + NetworkCredential) do the same thing as Remus's suggestion of myWebRequest.Headers.Add("Authorization", ...)?
Greg
@Gonzalo: the MSDN spec say it doesn't (on first request), also my personal experience backs up the MSDN claim. Did you test and validate your claim?
Remus Rusanu
I think you should match the user experience in your test. The WebRequest will be redirected to a login page, you read the content, validate that is the login form you expect, then do a POST with username+password data, validate that the reponse redirected you to the target page and you have an auth cookie (which is what most likely RR uses to track your 'forms' auth) and then make sure you add this cookie to subsequent test WebRequests. Make your tests behave exactly like the user+browser, this is my 2c.
Remus Rusanu
@Remus - understand Remus. In fact when I posted this I kind of thought I might have to do something like this and I was fishing for some sample code re how to do it. So it sounds like the .NET classes don't support an automated forms authentication based approach then? (i.e. effectively automating the steps you)
Greg
Not that I know of. But with the help of the HtmlAgility (http://www.codeplex.com/htmlagilitypack) is fairly easy parse a HTML form, and packing the values and submit (POST) the form is also reasonably easy, eg. http://stackoverflow.com/questions/726710/fake-a-form-submission-with-c-webclient
Remus Rusanu
Ultimately Basic and Digest are RFC backed standards and they specify how the browser should track the authentication (send the Authorization header). 'Forms' is more of a convention, there cleraly isn't any standard behind it: the 'login form' can be any form on the login page, it can have any field names for 'user' and 'password', any submit url and the tracking of identity is usually done by some arbitrarily named cookie. Throw in Ajax based 'forms' authentication and I really don't see a standard client component any time soon...
Remus Rusanu
@Remus Rusanu: yes, I tested it long time ago when I implemented the authentication modules and most of HttpWebRequest for the mono project.
Gonzalo
@Gonzalo: Mono spec differ from MSDN spec, they do not specify 'With the exception of the first request'. I have the traffic captured packed showing the first GET does not have the Authorize header, on a WebRequest with NetworkCredential and PreAuthenticate.
Remus Rusanu