Filters - By Narendra Shrestha
Filters
are one of the elegant features available in .net framework. They are one of
the excellent examples of cross-cutting concern where entity or functionality are
scattered all over the application and doesn’t fit neatly in one place. Filter
makes writing MVC application becomes much easier and lot cleaner. We must have
all used filters in the MVC framework at some point. To recall one, for example
“Authorize” filter, which is applied to actions or controller, to
guarantee secure access to the limited users. One more advantage of filter is
the elimination of the code duplication. For example the below actions in
“Admin” controller are absolutely fine.
public ActionResult ShowActiveUsers()
{
if
(Request.IsAuthenticated && User.Identity.IsAuthenticated)
{
return
View();
}
return
View("UnAuthorized");
}
public ActionResult EditAccounts()
{
if
(Request.IsAuthenticated && User.Identity.IsAuthenticated)
{
return
View();
}
return View("UnAuthorized");
}
Listing 1:-Typical “admin” controller without “Authorize”
filters
In
above listing, typical actions most likely to be found within “Admin”
controller are anticipated. These actions include displaying the list of active
users or more precisely preventing the unknown users from critical admin
functionalities. The same effect can be achieved by implementing “Authorize” filter which is a .net attribute filter. The code
implementing this filter is listed as below:-
[Authorize]
public ActionResult ShowActiveUsers()
{
return
View();
}
[Authorize]
public ActionResult EditAccounts()
{
return
View();
}
Listing 2:- Elimination of code duplication after application of “Authorize” filter
Needless to mention again, how much neater the code looks in listing 2 in
comparison to listing 1. How many of you have noticed sudden disappearance of
concern for the view “Unathorized”. It’s the
beauty of the “Authorize” filter. The failed
authorization is handled in the “Web.config” file of the MVC application.
<authentication mode="Forms">
<forms loginUrl="~/Home/UnAuthorized" timeout="2880" />
</authentication>
Listing 3:-Specifying the URL to redirect in case of unauthorized access in “Web.Config”.
Whenever, there is an unauthorized access, the user will be prompted with “Unauthorized”
page to provide valid credentials.
More commonly,
filters are .net attributes that add extra information to the request
processing pipeline.
What are attributes then?
The attributes are .net class derived from
the namespace “System.Attribute”. By deriving from “Attribute” class you can
create our own attributes. On top of this, you can declare your own properties,
methods and fields within your class. These properties convey information
during the runtime. While creating, make sure that the term “Attribute” is
appended to the name of your class. It’s a compulsory naming convention for the
custom attributes. For example “AuthorizeAttribute” is the name of the authorization filter
attribute in the MVC framework. However, when using this filter, we simply tend
to use “[Authorize]”. Attributes is one of the efficient technique in .net to improvise
filters.
Types of filters in MVC
MVC Framework supports four different kinds
of filters shown below:-
Filter
|
Interface
|
Default implementation
|
Roles
|
Authorization
|
IAuthorizationFilter
|
AuthorizeAttribute
|
Runs first and before any other filters or
action methods
|
Action
|
IActionFilter
|
ActionFilterAttribute
|
Runs before and after the execution of the
action method
|
Results
|
IResultFilter
|
ActionFilterAttribute
|
Runs before and after the action result is
executed
|
Exception
|
IExceptionFilter
|
HandleErrorAttribute
|
Runs when the exception is thrown from
other filter, action methods or action results.
|
Table 1:-Filters in MVC
Authorization Filter
As mentioned earlier, this filter runs prior
to any other filters and action methods. This filter is intended for the
authorization policy and restricts unauthorized users from secured methods. To
implement your own custom authorization filter, you need to implement the
interface “IAuthorizationFilter”. The following code lists the skeletal code for this interface.
using System;
namespace
System.Web.Mvc
{
// Summary:
// Defines the methods that are required for
an authorization filter.
public interface IAuthorizationFilter
{
// Summary:
// Called when authorization is required.
//
//
Parameters:
// filterContext:
// The filter context.
void
OnAuthorization(AuthorizationContext
filterContext);
}
}
Listing 4:-Skeletal code for the interface “IAuthorizationFilter”
However, you will barely find yourself in
such need. The default authorization filter available in MVC framework is fully
capable of ensuring safe authorization policies. These default authorization
filters are thoroughly inspected and tested by the Microsoft. There’s no harm
implementing your own, but there comes risk when you do so. An untested corner
or any undetected loophole in your custom authorization policies can cost you
unprecedented damage. Also, you are on safer side with default implementation.
At least you can be relaxed with the fact that the Microsoft will be
responsible for loopholes.
Instead of implementing “IAuthorizationFilter”, the much elegant method is to derive from “AuthorizeAttribute”. This way we
can be sure that we do not fiddle with default behavior of the authorization
filter. We create our own authorization attribute class called “MyAuthorizationAttribute”, derive it from “AuthorizeAttribute” and place it within “Infrastructure”
folder.
The behavior required is to provide secure access
to only unblocked IP addresses. It’s more likely to be implemented on the
intranet applications where all systems are interlinked under the same subnet
mask. The code for this custom authorization filter is:-
using System;
using
System.Collections.Generic;
using System.Linq;
using System.Web;
using
System.Web.Mvc;
namespace
Filters.Infrastructure
{
public class MyAuthorizationAttribute
: AuthorizeAttribute
{
private
List<string>
blockedIps;
protected
override bool
AuthorizeCore(HttpContextBase httpContext)
{
LoadBlockIpAddresses();
return
(!blockedIps.Contains(httpContext.Request.UserHostAddress));
}
public void LoadBlockIpAddresses()
{
blockedIps = new List<string>();
blockedIps.Add("127.0.0.2");
}
}
}
Listing 5:- “MyAuthorizationAttribute” class
The list of the block IP addresses are maintained through method “LoadBlockIpAddresses” and string list “blockedIps”. If the client’s IP
address is not contained within blocked list, he/she has access. The “AuthorizeCore” method of the “AuthorizeAttribute” class is the one to be overridden. The parameter to this method is of
type “HttpContextBase” giving access
to various request and response variables. Be sure to keep your default route as
“Home” controller and “Index” action
In the “Home” controller, there are two action method listed as below
using System;
using
System.Collections.Generic;
using System.Linq;
using System.Web;
using
System.Web.Mvc;
using
Filters.Infrastructure;
namespace
Filters.Controllers
{
public class HomeController
: Controller
{
//
// GET:
/Home/
[MyAuthorization]
public ActionResult Index()
{
return
View();
}
public ActionResult UnAuthorized()
{
return
View("Unauthorized");
}
}
}
Listing 6:-“Home” controller
As discussed previously, action “UnAuthorized” is meant to handle the request for unauthorized access. In our case it
simply renders the view named “Unauthorized”. For unauthorized access, the URL pointing to this action has been
specified in the “authentication” tag of the “Web.config” configuration.
The most important thing to take into account is the “Index” action where
custom filter attribute is applied i.e. “[MyAuthorization]”. Thus, when we access the
URL /Home/Index, the custom filter “MyAuthorizationAttribute” runs first and return the Boolean value based on whether the user’s IP
address is blocked or not. In order to use “[MyAuthorization]” attribute, the namespace “Filters.Infrastructure” must be bounded. The views incorporated are “Index.cshtml” for “Index”
action and “Unathorized.cshtml” for “Unathorized” action. However, the view for
“Unathorized” action is placed within “Shared” folder because there is no
strict distinction regarding its origin or placement.
Both of these views are listed as below.
Both of these views are listed as below.
@{
ViewBag.Title = "Index";
}
<h2>Your computer has access</h2>
Listing 7:-“Index.cshtml”
@{
ViewBag.Title = "Unauthorized";
}
<h2>You are not authorized</h2>
Listing 8:-“Unathorized.cshtml”
Before running the project, you need to make note of your system’s IP
address. By adding any other IP addresses to the blocked IP addresses list, and
redirecting to URL “Home/Index” generates following output.
Figure 1:- When user’s IP address isn’t included in the blocked list (user
has access)
Next include your own IP address within the blocked list. This should
prevent you from accessing index action and you will be taken to “Unathorized”
view as shown below.
Figure 2:-When user’s IP address is listed in the blocked list (user has no
access)
One of the prime reasons for the developers to write their own
authorization policy is because of “AuthorizationContext” parmeter within the “OnAuthorization” method of
interface “IAuthorizationFilter”. Since “AuthorizationContext” is derived from “ControllerContext”, we get access to route
data, controller specific properties etc within this method. This tempts
developers to use their own authorization policies.
Default Authorization
Filter
Default Authorization filter can fulfill our
requirement and we don’t even need to bother about any custom implementation.
Referring table 1, “AuthorizeAttribute” is the default implementation of the authorization filter. “AuthorizeAttribute” filter has its
own properties which are most like to be used. They are:-
·
“Users” of type “string” used to list
the name of the permitted users in comma separated format
·
“Roles” of type “string” used to list
the permitted roles of the users in comma separated format
To be able to use roles and authentication, we need to make some changes in
our “Web.config” configuration file as listed below.
<authentication mode="Forms">
<forms loginUrl="~/Home/UnAuthorized" timeout="2880">
<credentials>
<user name="name" password="name"/>
</credentials>
</forms>
</authentication>
<roleManager enabled="true" cacheRolesInCookie="true" />
Listing 9:-changes in “Web.config” file for authentication and role.
For simplicity we added “credentials” tag and hard-coded the username and password. I.e. name as “name” and password as “name”. Similarly, we enabled
role manager inside “roleManager” tag. We agreed
to cache our roles in cookies by setting “cacheRolesInCookie” to true. In real scenarios, for role management you will be using
advanced role providers like SQL role provider.
Our focal point at this moment is on the action “AccessSafe” in “Home”
controller. In order to be able to access this action, the user must be logged
in and must belong to “admin” role. We made following changes inside the “Home”
controller:-
public ActionResult Index()
{
//if
(!Roles.RoleExists("admin"))
//{
// Roles.CreateRole("admin");
// Roles.AddUsersToRole(new[] {
"name" }, "admin");
//}
//FormsAuthentication.Authenticate("name",
"name");
//FormsAuthentication.SetAuthCookie("name",
false);
return
View();
}
public ActionResult UnAuthorized()
{
return
View("Unauthorized");
}
[Authorize(Users="name",Roles="admin")]
public string AccessSafe()
{
return "safe accessed successfully!!!";
}
Listing 10:-Changes made to “Home” controller
First of all “[MyAuthorization]” custom attribute has been removed from the Index action to avoid
confusion. The new action “AccessSafe” is added and is included with attribute
“AuthorizeAttribute”.
The values of the authorize attribute properties are set as named
parameters. The allowed usernames are set to “name” and roles are set to “admin”. Run the project and visit the URL “/Home/AccessSafe”. We will be shown following
output:-
Fig 3:-Unauthorized access of action “AccessSafe” within “Home” controller
When we access this action without authentication, the default
authorization filter prevents us from doing so. Instead we are taken to URL
“/Home/UnAuthorized” as stated in the configuration file.
Uncomment the code of the “Index” action in listing 10. We used “Roles” static class from namespace “System.Web.Security” to work with roles. First of all, we check if the role “admin” exists. If it doesn’t we add this
particular role to the list. Then we
bind username “name” with the role “admin” by using “AddUsersToRole” method. Remember these roles are cached in cookies because of our setting
in the configuration file.
“FormsAuthentication” static class
also belongs to namespace “System.Web.Security”. “Authenticate” method of static is used in order to
authenticate users. The username and password are hard-coded here. So we don’t
require authentication result returned by method “Authenticate” which is of Boolean type. After all we know that the authentication will
succeed. In ideal situation we may be retrieving these data from model binder along
with POST request. The authentication is persisted through cookie by method “SetAuthCookie”.
Since “Index” action of “home” controller is default route in the route
table, we can be sure that we will be authenticated as soon as our project
runs. When “Index” view has completely loaded, we can redirect to URL
“/Home/AccessSafe”. We will see the following output:-
Figure 4:- Authorized to access the action “AccessSafe” within “Home”
controller
In an actual case, the default authorization
filter is used to restrict the user based on their roles. For example “Guest”
will have very limited access, “User” will have fair access and “admin” will
have unlimited access. Although the idea of custom authorization filter sounds
tempting, we shouldn’t attempt something like that until we are very determined
or have no choice.
No comments:
Post a Comment