Here is the simple step to secure your ASP.NET MVC site :
1. Membership – Authentication / Authorization
These are two separate security tasks. Authentication deals with identifying who is using the site. This typically involves a login process. Once the user has logged in, then authentication relies on security tokens sent between the browser and the server. Stealing of security tokens or tricking the user’s own browser to use the security token for evil are two major sources of security holes that must be addressed. Authorization is usually the process of assigning users roles (Admin, User…etc) that allow access to certain features of the application.
ASP.NET MVC leverages the ASP.NET authentication / authorization framework. This was configured using the Web.Config file as follows:
<
authentication
mode
=
"Forms"
>
<
forms
loginurl
=
"~/Account/LogOn"
timeout
=
"2880"
>
</
forms
></
authentication
>
<
membership
defaultprovider
=
"XmlMembershipProvider"
>
<
providers
>
<
clear
></
clear
>
<
add
name
=
"XmlMembershipProvider"
type
=
"XmlMembershipProvider"
xmlfilename
=
"~/App_Data/users.xml"
minrequiredpasswordlength
=
"6"
minrequirednonalphanumericcharacters
=
"0"
>
</
add
></
providers
>
</
membership
>
The above configuration does two things. It sets the authentication mode to “forms” and then configures a controller / action to the login form. It then configures the membership provider for the web site. In this case I am using an XML based membership provider, but many others exist.
In ASP.NET MVC forcing authentication is correctly done using the “Authorize” attribute. Some controller / action combinations may not require authentication. For instance, placing the “Authorize” attribute on the “Account/LogOn” controller / action would not allow the user to reach the login view with out being authorized first. Not very useful! The “Authorize” attribute can be placed on an action method or on the controller class itself. If placed on the controller class, then all actions will require authorization. The following demonstrates this:
[Authorize]
public
class
TaskController : Controller
{
/* code removed for brevity */
}
public
class
AccountController : Controller
{
public
ActionResult Login()
{
}
[Authorize(Roles=
"Admin"
)]
public
ActionResult DeleteUser()
{
}
[Authorize(Users=
"Bob"
)]
public
ActionResult DeleteAllUsers()
{
}
[Authorize]
public
ActionResult DeleteMyData(Task task)
{
if
(task.UserName == User.Identity.Name)
{
/* Code to delete user's data */
}
else
{
/* Invalid request */
}
}
}
In the above example, all action methods in the Task action controller will require authentication.
The “Authorize” attribute also takes parameters that allow you to narrow down access to specific users / roles. In the above example, the DeleteUser action on the Account controller requires the user to be a member of the “Admin” roles. The DeleteAllUsers action is requires the user name to be “Bob”.
The last example of the code above shows how you can filter the request to allow only the user to delete their own data. In this example, each data item contains a property indicating ownership. This is used to check against the current user.
2. Custom Filters
One major design goal of the APS.NET MVC framework extensibility. If you don’t like the default behavior of a feature, usually you are allowed to substitute your own. This is true of the “Authorize” attribute. Because this code is the front line of defense for your web site security caution must be taken when rolling your own security code. For instance, one security measure is to ensure the request came from your site. This can be accomplished using a customer filter.
public
class
IsPostedFromThisSiteAttribute : AuthorizeAttribute
{
public
override
void
OnAuthorization(AuthorizationContext filterContext)
{
if
(filterContext.HttpContext !=
null
)
{
if
(filterContext.HttpContext.Request.UrlReferrer ==
null
)
{
throw
new
System.Web.HttpException(
"Invalid submission"
);
}
if
(filterContext.HttpContext.Request.UrlReferrer.Host !=
"mysite.com"
)
{
throw
new
System.Web.HttpException(
"This form wasn't submitted from this site!"
);
}
}
base
.OnAuthorization(filterContext);
}
}
Go read that for details of the security hole this introduces. The following code is an example that opens up this security hole:
<
a
class
=
"confirm_delete hoverbox"
href="<%= Url.Action('DeleteTask', new { task.Id } ) %>">
<
img
alt
=
"delete"
title
=
"delete task"
src="<%= Url.Content('~/Content/Images/delete_hover.png') %>">
</
a
>
Ignore for now the AntiForgeryToken markup as this will be covered in the next section. Here the delete feature is implemented using an input tag (of image type). This input tag causes the form to POST back.
The important bit to grasp is that the delete action is no longer called on a HTTP GET request. During an HTTP GET request, we have no opportunity to filter a request from a legitimate user from that of someone trying to do evil. The HTTP POST is more secure because we can embed data into the web site during the HTTP GET that can be used to discriminate against the evil doers.
ASPHostPortal.com is Microsoft No #1 Recommended Windows and ASP.NET Spotlight Hosting Partner in United States. Microsoft presents this award to ASPHostPortal.com for ability to support the latest Microsoft and ASP.NET technology, such as: WebMatrix, WebDeploy, Visual Studio 2012, .NET 4.5.1/ASP.NET 4.5, ASP.NET MVC 5.0/4.0, Silverlight 5 and Visual Studio Lightswitch. Click here for more information
4. Anti-Forgery Token
In the above delete task form, we injected into the mark up an anti-forgery token on the HTTP GET request. This is key to thwarting disingenuous requests on the HTTP POST. The following is the “DeleteTask” action method.
[AcceptVerbs(HttpVerbs.Post)]
[IsPostedFromThisSite]
[ValidateAntiForgeryToken]
public
RedirectToRouteResult DeleteTask(Guid id)
{
Task task = _taskRepository.Retrieve(id);
if
(task !=
null
)
{
if
(task.UserName == User.Identity.Name)
{
/* Delete code ... */
}
else
{
/* Invalid request ... */
}
}
else
{
/* Invalid request ... */
}
return
RedirectToAction(
"List"
);
}
This action method is decorated with the following attributes:
- AcceptVerbs – This attribute allows the routing engine to differentiate between the HTTP GET and POST action methods of the same name.
- IsPostFromThisSite – This attribute was covered in a previous section and validates the original HTTP GET came from the same site that is handling the HTTP POST.
- ValidateAntiForgeryToken – This attribute examines the token we embedded on the HTTP GET to ensure it is valid