4

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

Greg
  • 34,042
  • 79
  • 253
  • 454

2 Answers2

16

WebRequest does not send credentials unless challenged by the site. The site should first respond with 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.

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

See HttpWebRequest not passing Credentials

Community
  • 1
  • 1
Remus Rusanu
  • 288,378
  • 40
  • 442
  • 569
  • Btw, WebRequest will send the credentials in the first request for Basic authentication if PreAuthenticate is used – Gonzalo Dec 01 '09 at 23:21
  • 2
    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 Dec 01 '09 at 23:26
  • 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 Dec 01 '09 at 23:48
  • 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 Dec 01 '09 at 23:52
  • 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 Dec 02 '09 at 00:01
  • 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 Dec 02 '09 at 00:05
  • 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 Dec 02 '09 at 00:05
  • 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 Dec 02 '09 at 00:07
  • @Remus Rusanu: he's not using a CredentialCache. And, really, PreAuthenticate + NetworkCredential sends the Authorization header in the first request. – Gonzalo Dec 02 '09 at 00:08
  • 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 Dec 02 '09 at 00:12
  • 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 Dec 02 '09 at 00:18
  • @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 Dec 02 '09 at 00:18
  • 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 Dec 02 '09 at 00:22
  • @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 Dec 02 '09 at 00:35
  • 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 Dec 02 '09 at 00:41
  • 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 Dec 02 '09 at 00:54
  • @Remus Rusanu: yes, I tested it long time ago when I implemented the authentication modules and most of HttpWebRequest for the mono project. – Gonzalo Dec 02 '09 at 00:58
  • @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 Dec 02 '09 at 01:38
2

If someone is looking for the server side fix then just add

HttpContext.Response.Headers.Add("WWW-Authenticate", "Basic realm=\"MyRealm\"");

after you set the StatusCode = 401

VladL
  • 12,769
  • 10
  • 63
  • 83