Integration Testing a Web App with Forms Authentication

This turned out to be harder than I thought. I had to not only figure out how to post form fields using .Net code but also handle anti-forgery cookies and hidden fields.

When you request a protected URL, the browser is redirected to the login page. The header of this response contains a Set-Cookie header and the body a hidden input. When the login form is posted, these must be sent back and match.

Here is some example code that does all this:

        private string LogIn(string startUrl)
        {
            try
            {
                //var proxy = new WebProxy("127.0.0.1", 8888); // Fiddler
                IWebProxy proxy = null;

                // Make the request. This will result in the login page being returned.
                var request = WebRequest.CreateHttp(startUrl);
                request.Proxy = proxy;
                var response = (HttpWebResponse)request.GetResponse();
                Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
                
                // Get request verification token cookie:
                string setCookie = response.Headers["Set-Cookie"];
                Assert.That(setCookie, Is.Not.Null);
                var requestVerificationTokenCookie = setCookie.Split(';')[0];

                // Get form action to be posted to later:
                string responseBody = new StreamReader(response.GetResponseStream()).ReadToEnd();
                var regex = new Regex("form action=\"([^\"]*)\"");
                Assert.That(regex.IsMatch(responseBody));
                string formAction = regex.Match(responseBody).Groups[1].Value;
                Assert.That(formAction, Is.Not.Null);

                // Get request verification token form field:
                regex = new Regex("<input name=\"__RequestVerificationToken\" .* value=\"([^\"]*)\"");
                Assert.That(regex.IsMatch(responseBody));
                string requestVerificaitonToken = regex.Match(responseBody).Groups[1].Value;
                
                // Post the login form:
                var baseUri = new Uri(Properties.Settings.Default.AppUrl);
                var localUri = new Uri(formAction, UriKind.Relative);
                var loginUri = new Uri(baseUri, localUri);
                request = WebRequest.CreateHttp(loginUri);
                request.Proxy = proxy;
                request.Method = HttpMethod.Post.Method;
                request.Headers.Add(HttpRequestHeader.Cookie, requestVerificationTokenCookie);
                string postData = $"__RequestVerificationToken={requestVerificaitonToken}&UserId={Properties.Settings.Default.UserId}&Password={Properties.Settings.Default.Password}";
                byte[] byteArray = Encoding.UTF8.GetBytes(postData);
                request.ContentType = "application/x-www-form-urlencoded";
                request.ContentLength = byteArray.Length;
                Stream dataStream = request.GetRequestStream();
                dataStream.Write(byteArray, 0, byteArray.Length);
                dataStream.Close();
                request.AllowAutoRedirect = false; // Prevents redirecting back to returnUrl.
                response = (HttpWebResponse) request.GetResponse();
                Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Found)); // Redirect to original url
                setCookie = response.Headers["Set-Cookie"];
                Assert.That(setCookie, Is.Not.Null);
                var sessionCookie = setCookie.Split(';')[0];
                return sessionCookie;
            }
            catch (WebException ex)
            {
                Assert.Fail(ex.ToString());
                return null;
            }
        }

Now, this can be used in a test like this:

        [Test]
        public void Test1()
        {
            string url = "http://localhost/...";
            string sessionCookie = LogIn(url);
            var response = Request(sessionCookie, url);
            Assert.That(response, Contains.Substring("(Some string that is always in correct response)"));
        }

        private static string Request(string sessionCookie, string url)
        {
            var webClient = new WebClient();
            webClient.Headers.Add(HttpRequestHeader.Cookie, sessionCookie);
            string response = webClient.DownloadString(url);
            return response;
        }
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s