Earlier this year, I was working on an iOS Application that interacted with a running ASP.NET MVC site and the need arose for the iPhone user to authenticate so that they could access some of the Actions within a Controller decorated with the [Authorize] attribute.
This post will cover how to create a simple cookie-aware extension of the WebClient class that will authenticate and persist this authentication for the duration of the WebClient to allow access to secure areas of your MVC Application without the need for re-authenticating for each request.
The Problem
You are using a very basic ASP.NET MVC Application with integrated Forms Authentication and the need arises for you to authenticate and access multiple controller actions, which require authentication, using a single WebClient instance.
The Solution
In order to handle this issue, we will need to create a custom WebClient class that will persist our authentication token for the duration of our visit based on parameters that were passed in. This can be accomplished by creating a class that will inherit from the WebClient class and will have a specific container to house the authentication cookie :
// Cookie-Aware WebClient
public class CookieAwareWebClient : WebClient
{
// An aptly named container to store the Cookie
public CookieContainer CookieContainer { get; private set; }
public CookieAwareWebClient()
{
CookieContainer = new CookieContainer();
}
}
In its current state – this fails to actually do anything except create an extra CookieContainer property that we can’t really do anything with as we will need to determine a way to actually populate the authentication cookie and store it within the WebClient from any of the Requests that are made through the client itself.
This can be handled by adding the following snippet of code within the CookieAwareWebClient
class :
protected override WebRequest GetWebRequest(Uri address)
{
// Grabs the base request being made
var request = (HttpWebRequest)base.GetWebRequest(address);
// Adds the existing cookie container to the Request
request.CookieContainer = CookieContainer;
return request;
}
The above code will associate the current Request that is being made from the WebClient and add the appropriate cookie to handle any future requests made by the client.
Digging in the Cookie Jar
The following example details how to use the newly created CookieAwareWebClient
instance within an MVC environment by allowing the current user (in this instance from an iOS Application built through Xamarin) to authenticate using the standard AccountController available in ASP.NET MVC and then call a method from a Controller that is decorated with the [Authorize]
Attribute.
Let’s take a look at exactly what we want to access through our iOS call :
[Authorize]
public class SecureController : Controller
{
// A secure method that will be accessed through
// your iOS Application
public ActionResult YourSecureMethod(string data)
{
return Content(String.Format("{0} was passed in.",data);
}
}
As you can see there isn't really anything special about the Controller or the Action besides the [Authorize]
decoration at the Controller level, which is great because we don’t really want to have to do anything special to handle this functionality.
Within your iOS application, we are going to make two calls (the first to authenticate and the second to make the actual call itself) within an instance of the custom WebClient :
// Create an instance of your new CookieAware Web Client
using (var client = new CookieAwareWebClient())
{
// Authenticate (username and password can be either
// hard-coded or pulled from a settings area)
var values = new NameValueCollection{
{ "UserName", username },
{ "Password", password }
};
// Perform authentication - after this has been
// performed the cookie will be stored within the Web Client
client.UploadValues(new Uri("http://www.yourdomain.com/Account/LogOn/"), "POST", values);
// Authentication worked! Access the secure area (you
// can use a variety of methods here such as UploadFile,
// UploadValues, etc.)
client.UploadString(new Uri("http://www.yourdomain.com/Secure/YourSecureMethod"), "POST", "Example Message");
}
And that’s really all you need as you could call a multitude of other Controller Actions within the context of your WebClient. It should be noted that your authentication values will only persist during the using statement and any further attempts to access your application outside of it will require additional authentication.
More Chocolate Chips to Add to the Cookies.
This is just one of the many examples of the Web Client being extended to assist with a very simple requirement that previous several redundant calls from being made within a simple Request. Another feature that I found to be incredibly helpful is implementing a Timeout mechanism for your WebClient.
Timing out can be essential when dealing with requests from iOS devices, as most users don’t want to sit there and wait indefinitely for a password that they accidentally left the CAPS LOCK key on for to be rejected. For this reason, implementing a timeout can put a stop to a request after a specific amount of time and notify the user that they will need to retry their request.
All you will need to do to handle this will be to add an integer property to the WebClient class :
// An optional timeout property
private int? _timeout = null;
public int? Timeout
{
get { return _timeout; }
set { _timeout = value; }
}
along with a method on your WebClient to handle setting it :
public void SetTimeout (int timeout)
{
_timeout = timeout;
}
and then finally, you’ll need to actually use it when your WebRequests are being made, so you can add the logic in there as well :
protected override WebRequest GetWebRequest(Uri address)
{
var request = (HttpWebRequest)base.GetWebRequest(address);
request.CookieContainer = CookieContainer;
// Sets a timeout for the Request if the
// timeout property is available
if (_timeout.HasValue)
{
request.Timeout = _timeout.Value;
}
return request;
}
So after all that, now you would just need to set the _timeout
property (if needed) after declaring your WebClient :
using (var client = new CookieAwareWebClient())
{
// Times out after 5 minutes
client.SetTimeout (5 * 60 * 1000);
// I found that on iOS devices that the KeepAlive
// header can be helpful (but it is likely not
// completely necessary in most instances)
client.Headers [HttpRequestHeader.KeepAlive] = "true";
// Additional code omitted for brevity
}
Too. Much. Cookie.
If this post made you hungry and you are too overwhelmed or are edging towards a cookie-coma, don’t fret. I won’t make you go through the extended trouble of copy-pasting the entire class above from all of those separate snippets.
You can download the class from GitHub below in case the need ever arises that you should require something even remotely like it or you would want to extend it further :