84

I have an application that reads parts of the source code on a website. That all works; but the problem is that the page in question requires the user to be logged in to access this source code. What my program needs a way to initially log the user into the website- after that is done, I'll be able to access and read the source code.

The website that needs to be logged into is: mmoinn.com/index.do?PageModule=UsersLogin

starball
  • 20,030
  • 7
  • 43
  • 238
  • So, I can think of a bunch of ways to do this... Does the C# program request the 'code' directly from the server via HTTP or do you piggy back on the browser application or what? A bit more info is needed. – Mitch Baker May 30 '09 at 23:16
  • The program uses WebClient.DownloadString("URL") –  May 30 '09 at 23:24

4 Answers4

116

You can continue using WebClient to POST (instead of GET, which is the HTTP verb you're currently using with DownloadString), but I think you'll find it easier to work with the (slightly) lower-level classes WebRequest and WebResponse.

There are two parts to this - the first is to post the login form, the second is recovering the "Set-cookie" header and sending that back to the server as "Cookie" along with your GET request. The server will use this cookie to identify you from now on (assuming it's using cookie-based authentication which I'm fairly confident it is as that page returns a Set-cookie header which includes "PHPSESSID").


POSTing to the login form

Form posts are easy to simulate, it's just a case of formatting your post data as follows:

field1=value1&field2=value2

Using WebRequest and code I adapted from Scott Hanselman, here's how you'd POST form data to your login form:

string formUrl = "http://www.mmoinn.com/index.do?PageModule=UsersAction&Action=UsersLogin"; // NOTE: This is the URL the form POSTs to, not the URL of the form (you can find this in the "action" attribute of the HTML's form tag
string formParams = string.Format("email_address={0}&password={1}", "your email", "your password");
string cookieHeader;
WebRequest req = WebRequest.Create(formUrl);
req.ContentType = "application/x-www-form-urlencoded";
req.Method = "POST";
byte[] bytes = Encoding.ASCII.GetBytes(formParams);
req.ContentLength = bytes.Length;
using (Stream os = req.GetRequestStream())
{
    os.Write(bytes, 0, bytes.Length);
}
WebResponse resp = req.GetResponse();
cookieHeader = resp.Headers["Set-cookie"];

Here's an example of what you should see in the Set-cookie header for your login form:

PHPSESSID=c4812cffcf2c45e0357a5a93c137642e; path=/; domain=.mmoinn.com,wowmine_referer=directenter; path=/; domain=.mmoinn.com,lang=en; path=/;domain=.mmoinn.com,adt_usertype=other,adt_host=-

GETting the page behind the login form

Now you can perform your GET request to a page that you need to be logged in for.

string pageSource;
string getUrl = "the url of the page behind the login";
WebRequest getRequest = WebRequest.Create(getUrl);
getRequest.Headers.Add("Cookie", cookieHeader);
WebResponse getResponse = getRequest.GetResponse();
using (StreamReader sr = new StreamReader(getResponse.GetResponseStream()))
{
    pageSource = sr.ReadToEnd();
}

EDIT:

If you need to view the results of the first POST, you can recover the HTML it returned with:

using (StreamReader sr = new StreamReader(resp.GetResponseStream()))
{
    pageSource = sr.ReadToEnd();
}

Place this directly below cookieHeader = resp.Headers["Set-cookie"]; and then inspect the string held in pageSource.

Matt Brindley
  • 9,739
  • 7
  • 47
  • 51
  • Thanks a lot for the detailed response; but there's still one part that I'm unsure of. Am I supposed to be changing something regarding "Set -cookie", "Cookie", or the "PHPSESSID" you posted? I tried simply using that code in a program with inputting my information, but it doesn't seem to be logging me in (I assume I'm screwing something up with the cookies). –  May 31 '09 at 02:35
  • The code should be OK to use verbatim. The server sets the cookie (in Set-cookie) and the client (that's you) sends the cookie back up as Cookie). The first thing to check is that the first POST actually logs you in, you may find the server expected another field in your form POST (as strange as it sounds, you sometimes need an empty field with the button's name). I've updated the post to show how to view the results of the POST. – Matt Brindley May 31 '09 at 02:41
  • I'm not sure what I was doing wrong the first time, but it works now! Thanks a lot for the help. –  May 31 '09 at 03:17
  • 1
    How can I determine if the user successfully authenticated? – Cyral Oct 27 '12 at 21:37
  • 2
    I know we're not supposed to put thanks here but man you saved my ass! +1 – Owen James Oct 11 '15 at 20:02
  • The type or namespace name 'WebRequest' could not be found. error. – vee Mar 08 '17 at 00:58
  • simple question : I'm trying to read data from my router , I think I manage to enter the router but can't read the next page to see data from it ? and I copy the code as is ... am I missing something? – Korenron Feb 24 '19 at 12:20
  • Before I go down the rabbit whole, is it possible to have two threads from the same app logged into the same domain using different credentials using the C# methods described above? – Bill Greer Nov 04 '19 at 19:51
40

You can simplify things quite a bit by creating a class that derives from WebClient, overriding its GetWebRequest method and setting a CookieContainer object on it. If you always set the same CookieContainer instance, then cookie management will be handled automatically for you.

But the only way to get at the HttpWebRequest before it is sent is to inherit from WebClient and override that method.

public class CookieAwareWebClient : WebClient
{
    private CookieContainer cookie = new CookieContainer();

    protected override WebRequest GetWebRequest(Uri address)
    {
        WebRequest request = base.GetWebRequest(address);
        if (request is HttpWebRequest)
        {
            (request as HttpWebRequest).CookieContainer = cookie;
        }
        return request;
    }
}

var client = new CookieAwareWebClient();
client.BaseAddress = @"https://www.site.com/any/base/url/";
var loginData = new NameValueCollection();
loginData.Add("login", "YourLogin");
loginData.Add("password", "YourPassword");
client.UploadValues("login.php", "POST", loginData);

//Now you are logged in and can request pages    
string htmlSource = client.DownloadString("index.php");
Dima Stefantsov
  • 943
  • 1
  • 14
  • 20
Josh
  • 68,005
  • 14
  • 144
  • 156
  • When debugging, the (made it public) cookie is always empty. The website is for sure giving out cookies on the pages Im downloading. – C4d Jun 14 '16 at 20:42
  • Thanks, after serveral hours of finding a solution, this works! – Essej Jun 10 '17 at 20:17
9

Matthew Brindley, your code worked very good for some website I needed (with login), but I needed to change to HttpWebRequest and HttpWebResponse otherwise I get a 404 Bad Request from the remote server. Also I would like to share my workaround using your code, and is that I tried it to login to a website based on moodle, but it didn't work at your step "GETting the page behind the login form" because when successfully POSTing the login, the Header 'Set-Cookie' didn't return anything despite other websites does.

So I think this where we need to store cookies for next Requests, so I added this.


To the "POSTing to the login form" code block :

var cookies = new CookieContainer();
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(formUrl);
req.CookieContainer = cookies;


And To the "GETting the page behind the login form" :

HttpWebRequest getRequest = (HttpWebRequest)WebRequest.Create(getUrl);
getRequest.CookieContainer = new CookieContainer();
getRequest.CookieContainer.Add(resp.Cookies);
getRequest.Headers.Add("Cookie", cookieHeader);


Doing this, lets me Log me in and get the source code of the "page behind login" (website based moodle) I know this is a vague use of the CookieContainer and HTTPCookies because we may ask first is there a previously set of cookies saved before sending the request to the server. This works without problem anyway, but here's a good info to read about WebRequest and WebResponse with sample projects and tutorial:
Retrieving HTTP content in .NET
How to use HttpWebRequest and HttpWebResponse in .NET

Community
  • 1
  • 1
WhySoSerious
  • 1,930
  • 18
  • 18
2

Sometimes, it may help switching off AllowAutoRedirect and setting both login POST and page GET requests the same user agent.

request.UserAgent = userAgent;
request.AllowAutoRedirect = false;
TN.
  • 18,874
  • 30
  • 99
  • 157