Why can't I use HttpClient to log in to this ASP.NET website?

Viewed 1468

There's an ASP.NET website from a third party that requires one to log on. I need to get some data from the website and parse it, so I figured I'd use HttpClient to post the necessary credentials to the website, same as the browser would do it. Then, after that POST request, I figured I'd be able to use the cookie values I received to make further request to the (authorization-only) urls.

I'm down to the point where I can successfully POST the credentials to the login url and receive three cookies: ASP.NET_SessionId, .ASPXAUTH, and a custom value used by the website itself, each with their own values. I figured that since the HttpClient I set up is using an HttpHandler that is using a CookieContainer, the cookies would be sent along with each further request, and I'd remain logged in.

However, this does not appear to be working. If I use the same HttpClient instance to then request one of the secured areas of the website, I'm just getting the login form again. The code:

        const string loginUri = "https://some.website/login";

        var cookieContainer = new CookieContainer();
        var clientHandler = new HttpClientHandler() { CookieContainer = cookieContainer, AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate };
        var client = new HttpClient(clientHandler);
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));

        var loginRequest = new HttpRequestMessage(HttpMethod.Post, loginUri);

        // These form values correspond with the values posted by the browser
        var formContent = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("customercode", "password"),
            new KeyValuePair<string, string>("customerid", "username"),
            new KeyValuePair<string, string>("HandleForm", "Login")
        });

        loginRequest.Content = formContent;

        loginRequest.Headers.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393");
        loginRequest.Headers.Referrer = new Uri("https://some.website/Login?ReturnUrl=%2f");
        loginRequest.Headers.Host = "some.website";
        loginRequest.Headers.Connection.Add("Keep-Alive");
        loginRequest.Headers.CacheControl = new System.Net.Http.Headers.CacheControlHeaderValue() { NoCache = true };
        loginRequest.Headers.AcceptLanguage.ParseAdd("nl-NL");
        loginRequest.Headers.AcceptEncoding.ParseAdd("gzip, deflate");
        loginRequest.Headers.Accept.ParseAdd("text/html, application/xhtml+xml, image/jxr, */*");

        var response = await client.SendAsync(loginRequest);
        var responseString = await response.Content.ReadAsStringAsync();

        var cookies = cookieContainer.GetCookies(new Uri(loginUri));

When using the proper credentials, cookies contains three items, including a .ASPXAUTH cookie and a session id, which suggests that the login succeeded. However:

        var text = await client.GetStringAsync("https://some.website/secureaction");

...this just returns the login form again, and not the content I get when I log in using the browser and navigate to /secureaction.

What am I missing?

EDIT: here's the complete request my application is making and the request chrome is making. They are identical, save for the cookie values. I ran them through windiff: the lines marked <! are the lines sent by my application, the ones marked !> are sent by Chrome.

GET https://some.website/secureaction 
Connection: keep-alive  
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36  
Accept-Encoding: gzip, deflate, sdch, br 
Upgrade-Insecure-Requests: 1  
Host: some.website  
Accept-Language:nl-NL, 
>> nl;q=0.8,en-US;q=0.6,en;q=0.4

Accept: text/html, 
>> application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8

Cookie: 
<!      customCookie=7CF190C0; 
<!          .ASPXAUTH=37D61E47(shortened for readability); 
<!      ASP.NET_SessionId=oqwmfwahpvf0qzpiextx0wtb 
!>      ASP.NET_SessionId=kn4t4rmeu2lfrgozjjga0z2j;          
!>          customCookie=8D43E263; 
!>          .ASPXAUTH=C2477BA1(shortened for readability)

The HttpClient application get a 302 referral to /login, Chrome gets a 200 response containing the requested page.

1 Answers

As requested, here's how I eventually made it work. I had to do a simple GET request to /login first, and then do a POST with the login credentials. I don't recall what value exactly is being set by that GET (I assume a cookie with some encoded value the server wants), but the HttpClient takes care of the cookies anyway, so it just works. Here's the final, working code:

    const string loginUri = "https://some.website/login";

    var cookieContainer = new CookieContainer();
    var clientHandler = new HttpClientHandler() 
    { 
        CookieContainer = cookieContainer, 
        AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate 
    };

    var client = new HttpClient(clientHandler);
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));

    // First do a GET to the login page, allowing the server to set certain 
    // required cookie values.
    var initialGetRequest = new HttpRequestMessage(HttpMethod.GET, loginUri);
    await client.SendAsync(initialGetRequest);

    var loginRequest = new HttpRequestMessage(HttpMethod.Post, loginUri);

    // These form values correspond with the values posted by the browser
    var formContent = new FormUrlEncodedContent(new[]
    {
        new KeyValuePair<string, string>("customercode", "password"),
        new KeyValuePair<string, string>("customerid", "username"),
        new KeyValuePair<string, string>("HandleForm", "Login")
    });

    loginRequest.Content = formContent;

    loginRequest.Headers.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393");
    loginRequest.Headers.Referrer = new Uri("https://some.website/Login?ReturnUrl=%2f");
    loginRequest.Headers.Host = "some.website";
    loginRequest.Headers.Connection.Add("Keep-Alive");
    loginRequest.Headers.CacheControl = new System.Net.Http.Headers.CacheControlHeaderValue() { NoCache = true };
    loginRequest.Headers.AcceptLanguage.ParseAdd("nl-NL");
    loginRequest.Headers.AcceptEncoding.ParseAdd("gzip, deflate");
    loginRequest.Headers.Accept.ParseAdd("text/html, application/xhtml+xml, image/jxr, */*");

    var response = await client.SendAsync(loginRequest);
    var responseString = await response.Content.ReadAsStringAsync();

    var cookies = cookieContainer.GetCookies(new Uri(loginUri));
Related