Click here to Skip to main content
15,867,686 members
Articles / Web Development / HTML

Implement secure ASP.NET MVC applications

Rate me:
Please Sign up or sign in to vote.
4.94/5 (117 votes)
21 Jul 2014CPOL37 min read 372.8K   9.1K   339   39
This article discusses various aspects of ASP.NET MVC security and shows some tips to implement these elements in your applications.

Table of contents

  1. Introduction
  2. Background
  3. User authentication
    1. Using ASP.NET membership provider for user authentication
    2. Single sign on
    3. Using a third party identity provider
  4. Page access control
    1. Secure application using the standard action filters
    2. Implement custom security rules
      1. Creating custom security action filters
      2. Handling security errors
      3. Alternative approach - Inheriting the Authorization attribute
    3. Using third party libraries for page access control
    4. Control-level protection
      1. Control level protection using security filters
  5. Code Access Security
    1. Using standard security permissions
    2. Using custom permissions
  6. Forcing the HTTPS protocol
  7. Cross-site request forgery
    1. Preventing cross-site request forgery attacks
  8. Preventing cross-site scripting
    1. XSS vulnerable code - An example
    2. Using the Anti-XSS Library
  9. Conclusion

Introduction

When you implement security in web applications, you will need to think about a lot of security details. Some of the standard features you will need to implement are:

  1. Authentication of the user
  2. Page access control where you can decide what pages the user can open depending on his role or access rights
  3. Preventing security attacks

In this article, I will discuss some security elements and show you how they can be implemented in MVC applications.

Background

In the code, I will implement the following security elements:

  1. Login page that logs in user and puts the user name and role in the authentication cookie.
  2. Simulation of some custom business logic that determines whether the user can access a particular page.
  3. Filter that checks whether the user can access a page; if the user does not have enough access, he will be redirected to the login page.

This is everything that you need to have to implement access restrictions to pages in your application, and in the code, you will find a simple way to implement this using action filters.

For demonstration purpose, I have created a few actions where I will show you how access to pages is controlled. Some of the pages I have placed in the code sample are:

  • /Public/Index and /Public/Login that can be opened by everyone (public access).
  • /Registered/Index and /Registered/Home where registered users and administrators can come.
  • /Administrator/Index and /Administrator/Home that can be opened by the administrator.
  • /Administrator/Denied that can be opened by the administrator only but when it is opened, it will throw a security exception. This page is a simulation of the case when the user can access the page but some internal component have thrown a security exception.

Most of these views are fairly simple - each of them just outputs one word displaying which view is called. The login page (/Public/Login) contains a form that can be used for login, and /Public/Index is a landing page that has links to all other pages as shown in the following figure:

ASPNET-MVC-Security/SecurityFilters.png

Also, I will use three user roles - public, registered, and administrator, which are used to demonstrate page access control. In the code, I will show you how access can be denied on some of these actions depending on the user role.

There are also a few other pages where I have demonstrated how you can prevent some security attacks - you will see more details in the rest of the article.

User authentication

In order to secure your application, you will need to enable the user to login, and you will need to determine the role of a user.

In this example, I will use forms authentication so I will need to configure this in the web.config and set the login page. The web.config is shown in the following sample:

XML
<authentication mode="Forms">
      <forms loginUrl="~/Public/Login" />
</authentication>

This part of the web.config file defines that I will use Forms authentication (meaning that I will provide a form for login - in this case, this will be the "~/Public/Login" form). As an alternative, you can use Windows authentication.

The login form will contain two fields, where the user can enter his name and a select role:

HTML
<form method="post" action="#">
    <input type="text" name="Username" value="" />
    <select name="Role">
        <option value="">No role</option>
        <option value="Public">Public</option>
        <option value="Registered">Registered</option>
        <option value="Admin">Admin</option>     
    </select>
    <input type="submit" name="Login" value="Login" />
</form>

This form does not have an identity check but in your application you will probably replace this with username/password, check they match user details, and read the role from the database. This simple simulation will be enough "security check" for this article, but if you want to see a complete solution with role based security, see something like Forms Authentication and Role Based Authorization: A Quicker, Simpler, and Correct Approach.

Note that I have set the action of the form to # - this way the form will be posted back to the same URL and it will include any parameter that is posted to the login form. This is handy if the parameter ReturnURL="...." is sent to the login page, where ReturnURL is the URL of the origin page where the user should be redirected after he successfully logs in to the system. If you need to put an explicit URL in the action attribute, then it would be good to put the ReturnURL as a hidden field and pass it to the controller when the login page is submitted, as shown in the following example:

HTML
<form method="post" action="/Public/Login">
    <input type="hidden" name="ReturnURL" 
          value="@System.Web.HttpContext.Current.Request["ReturnURL"]" />
    <input type="text" name="Username" value="" />
    <select name="Role">
        <option value="Public">Public</option>
        <option value="Registered">Registered</option>
        <option value="Admin">Admin</option>     
    </select>
    <input type="submit" name="Login" value="Login" />
</form>

In this form, I have just taken a value from the ReturnURL request parameter and put it into the form's hidden field.

The login form will submit the username and role to the action that will put the username and role into the authentication cookie. An example of such kind of action is shown in the following listing:

C#
public ActionResult Login(string Username, string Role, string ReturnUrl)
{
    if (string.IsNullOrEmpty(Username))
        Username = "Unknown";
        //Default value that is set if nothing is entered

    var authTicket = new FormsAuthenticationTicket(1, Username, 
        DateTime.Now, DateTime.Now.AddMinutes(30), true, 
        Role);
    string cookieContents = FormsAuthentication.Encrypt(authTicket);
    var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, cookieContents)
                                {
                                    Expires = authTicket.Expiration,
                                    Path = FormsAuthentication.FormsCookiePath
                                };
    Response.Cookies.Add(cookie);

    if(!string.IsNullOrEmpty(ReturnUrl))
            Response.Redirect(ReturnUrl);

    return View("Index");
}

This code creates an authentication cookie and puts the username and role to the cookie. Details of creating a forms authentication cookie are already covered in other articles, so if you are not familiar with this, you can see more details here: Forms Authentication and Role Based Authorization: A Quicker, Simpler, and Correct Approach. If a return URL is provided, the user will be redirected there, otherwise it will be redirected to the index page.

Using the ASP.NET membership provider for user authentication

In the example above, I have not implemented any code that checks if the user is valid and that takes the user details (e.g., role). However, you will need to decide where the user information is placed and implement code that reads this information.

The simplest way to implement authentication is to use the ASP.NET Membership provider to handle user accounts. With the ASP.NET Membership provider, you will get all standard operations for authentication and user management such as:

  • bool Membership.ValidateUser(string username, string password) that returns true if username/password are valid
  • MembershipUserCollection Membership.GetUsersByName(string username) that searches for users by username
  • MembershipUserCollection Membership.GetUsersByEmail(string email) that searches for users by email
  • MembershipUser Membership.CreateUser(string username, string password) that creates a new user
  • void Membership.UpdateUser(MembershipUser user) that updates user data
  • void Roles.AddUserToRole(string username, strig roleName) that associates a user to a role
  • string[] Roles.GetRolesForUser(string username) returns a set of roles for a user
  • bool Roles.IsUserInRole(string username, string roleName) checks whether the user is in a particular role

There are a lot of other static methods available in the Membership and Roles classes that can be used, including a number of overloads for methods that are shown above.

However, these classes are only an interface to the actual user storage, so you will need to bind the membership provided to the actual storage.

Using the SQL Server membership provider

When you use the ASP.NET Membership provider classes, you will need to bind it to some data source where user information is placed. As an example, you can bind your Membership provider to a SQL Server database using SqlMembershipProvider. In order to setup SqlMembershipProvider, you will need to setup a connection to the database where user accounts will be stored and bind the provider to the connection string. All these configurations can be placed in the web.config file as shown in the following example:

XML
<configuration>
  <connectionStrings>
    <add name="UserDatabaseConnection"
         connectionString="CONNECTION STRING"
         providerName="System.Data.SqlClient" />
  </connectionStrings>
  <system.web>
    <membership>
      <providers>
        <clear />
        <add name="AspNetSqlMembershipProvider"
            type="System.Web.Security.SqlMembershipProvider"
            connectionStringName="UserDatabaseConnection" />
      </providers>
    </membership>
    <roleManager>
      <providers>
        <add
          name="SqlProvider"
          type="System.Web.Security.SqlRoleProvider"
          connectionStringName="UserDatabaseConnection" />
      </providers>
    </roleManager>
  </system.web>
</configuration>

You will need to define a connection string to the SQL Server database where user information will be placed (it is called UserDatabaseConnection in the example above) and add the Membership provider that will use this connection. If you wan to use roles associated to users for role based security, then you will need to configure the role manager too. There are a few other parameters for SqlMembershipProvider that can be set, but they are not shown. You can see more details about SqlMembershipProvider parameters and configuration in the MSDN articles about the SqlMembershipProvider class and SqlRoleProvider class.

SqlMembershipProvider uses a predefined structure of database tables and Stored Procedures that must exist in the database so this provider can read/update user information. In the following figure are shown tables that are required by SqlMembershipProvider:

ASPNET-MVC-Security/aspnet.png

If these tables are not present in the database, you will get an exception when you call any of the Membership methods. SQL membership procedures/tables can be easily added in the existing database; you just need to run the Aspnet_regsql.exe tool - see more details about this in the Using the SQL Membership with an ASP.NET Application article.

Creating a custom membership provider

If you do not want to use the standard schema, or if you have your own data structure that you need to use, you can create your own custom membership provider. All you need to do is inherit the MembershipProvider class and implement static membership methods you need in the application as shown in the following example:

C#
public class MyMembershipProvider : MembershipProvider
{   
    public override bool ValidateUser(string username, string password)
    {
        throw new NotImplementedException();
    }

    public override MembershipUser CreateUser(string username, 
       string password, string email, string passwordQuestion, 
       string passwordAnswer, bool isApproved, 
       object providerUserKey, out MembershipCreateStatus status)
    {
        throw new NotImplementedException();
    }

    public override MembershipUser GetUser()
    {
        throw new NotImplementedException();
    }
}

Here you can put any custom code that handles user information (e.g., reading from file, LDAP, Web Service, etc.). Once you create your own provider, you will need to register it in the web.config:

XML
<membership defaultProvider="CustomMembershipProvider">
  <providers>
    <clear/>
    <add name="MyMembershipProvider" 
        type="Security.MyMembershipProvider" />
  </providers>
</membership>

I have not included details about the implementation in this example because this is already explained in more detail in the Custom Membership Providers article. Also, you can take a look at the Custom MembershipProvider and RoleProvider Implementations that use Web Services article which shows how you can implement MembershipProvider and RoleProvider using Web Services.

Single sign on

There are common scenarios where your users use several applications where authentication is required but they don't want to login into each application separately. This scenario is shown in the following figure.

ASPNET-MVC-Security/sso.png

Imagine that you have different users accessing four sites where each site requires authentication. Instead of forcing the user to login to each application, you can allow them to login to one, and then access other applications. In the example above, the first user logins to www.site2.com and another to www.site3.com, and the remaining sites trust this authentication mechanism and allows free access.

This is common scenario in practice - in various applications you might see login via Facebook, Google, MSN live, or other applications where cookies provided by these applications are accepted.

Sharing Forms authentication cookies

The easiest way to implement single sign on is to share forms authentication cookies. One application provides a forms authentication cookie and another accepts it as its own cookie. If you want to use a common authentication ticket, you must manually generate the validationKey and decryptionKey values of the <machineKey> element in the machine level Web.config file. An example of these values are shown below:

XML
<machineKey  
      validationKey="21F090935F6E49C2C797F69BBAAD8402ABD2EE0B667A8B44EA7DD4374267A75D7
                      AD972A119482D15A4127461DB1DC347C1A63AE5F1CCFAACFF1B72A7F0A281B"   
      decryptionKey="ABAA84D7EC4BB56D75D217CECFFB9628809BDB8BF91CFCD64568A145BE59719F"
      validation="SHA1"
      decryption="AES"/>

By default values for validationKey and decryptionKey are auto-generated so they can isolate applications. Therefore you will need to generate your own values and share them across sites. See more details about configuring machineKey in the How to configure MachineKey in ASP.NET 2.0 MSDN article, where you can find a code sample that can be used for generating these random keys.

This will work fine if you have different applications placed on the same domain but on different virtual directories, such as:

  • site1.com/app1
  • site1.com/app2
  • site1.com/app3

In this case, all applications share the same cookies. However, this will not work if applications are placed in different sub-domains. E.g.:

  • app1.site1.com
  • app2.site1.com
  • app3.site1.com

Even if you allow different sites to decrypt the same cookies, they will be invisible to applications on other sub-domains because each application can use only their own domain cookies. To overcome this problem, you will need to define domains in the forms authentication tag in the web.config:

XML
<authentication mode="Forms">
  <forms loginUrl="~/Public/Login" domain="site1.com"/>
</authentication>

This attribute specifies the domain that will be set on the outgoing forms-authentication cookies. See more details about the forms element on the MSDN site.

In the most complicated case, when applications are placed on completely different domains, you will need to implement your own SSO solution. Details are out of then scope of this article, so you might take a look for details in some other article such as Single Sign On (SSO) for cross-domain ASP.NET applications or Single Sign-On (SSO) for .NET.

Using a third party identity provider

In some cases, you will not store user accounts in your system. In that case, user identities should be stored on some public identity provider such as Google, Facebook, Yahoo!, etc. Users do not want to login each time on separate systems if they are already logged in to one system. In that case, it is possible to check the user identity on the identity providers and authenticate the user on your site.

If you want a generic solution for third party authorization or if you don't want to choose a particular identity provider, then you should take a look at the OpenID protocol. The OpenID protocol is a mediator between your website and identity providers such as Google, Facebook, Yahoo!, etc. A user that has an account in any of these providers can access your site if you implement OpenID authentication. When the user logs in to your website using the OpenID protocol, the scenario for login is:

  1. User enters his OpenID into a login form.
  2. Browser then sends you to the OpenID provider to log in (e.g., Google, or Facebook).
  3. User logs in to the OpenID provider with his username and password.
  4. User confirms that the original website can use his identity.
  5. User is sent back to the original website.

I will not describe this in detail because there is a good article about the implementation of the OpenID protocol - you can find more information in the OpenID With Forms Authentication article.

Page access control

Once you implement proper authentication, you will need to control what pages users can access depending on the role.

ASP.NET MVC has a great feature that can be used to implement page access security - action filters. Action filters are classes that can be attached to controllers or particular actions which contain methods that are executed before or after an action is called, when an error occurs, etc. Filters are usually attached to actions in controllers using annotations, as shown in the following example:

C#
public class PublicController : Controller
{
    [Authorize]
    public string Home()
    {
        return "Home";
    }
}

In this example, the Authorize filter attribute is added to Home action of PublicController. Now you cannot call this action if you are not authorized.

You can find various usage of filters that log errors, track IP addresses, or display ads in Creating Custom Action Filters in ASP.NET MVC- CodeGuru. In this article, I demonstrate one usage of action filters - securing your application.

Action filters enable a centralized place for managing application-wide rules - one of this rules is controlling access to your application. In this article, I will create an action filter that controls whether the current user has enough permission rights to call a particular controller/actions in the application.

Secure application using standard MVC filters

The easiest way to implement security is to use filters that are already provided in the MVC framework. MVC comes with a number of filters that can be applied to your pages. One of the most important ones is the [Authorize] filter. In order to force authorization on some page, you can add the Authorize attribute on a particular action, something like in the following example:

C#
public class JovanController : Controller
{
    [Authorize(Users="Jovan")]
    public string Home()
    {
        return "Home";
    }
}

This code will allow only a user with name "Jovan" to call this controller.

Another way to apply authorization is to put the attribute on the entire controller class, as shown in the following example:

C#
[Authorize(Roles="Administrator")]
public class AdministratorController : Controller
{
    public string Home()
    {
        return "Home";
    }
}

In this case, actions in the controller can be called only if the user that calls actions has a role "Administrator".

A third way is to apply a global attribute that will be used in all controllers and all actions. You can apply filters globally by adding a filter as a global filter in the RegisterGlobalFilters method of the Global.asax.cs file - an example of the code is shown in the following listing:

C#
public class MvcApplication : System.Web.HttpApplication
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new AuthorizeAttribute());
    }
}

The Authorize attribute will be applied to all controllers and classes in the application.

Implement custom security rules

The standard MVC filters are fine if you can hard-code roles and users in the application, but if you need to create custom security like IP restricted access, you will need to define your own filter actions. In this application, I will assume that I have a class that determines whether some user that comes from some IP address can access a particular action in some controller. For demonstration purposes, I created the following class:

C#
public class PageAccessManager
{
    public static bool IsAccessAllowed(string Controller, 
           string Action, IPrincipal User, string IP)
    {
        if (Controller == "Public")
            return true;
        if (Controller == "Registered" && Action == "Login")
            return true;
        if (Controller == "Registered" && Action != "Login" 
                && (User.IsInRole("Registered") || User.IsInRole("Admin")))
            return true;
        if (Controller == "Admin" && User.IsInRole("Admin"))
            return true;

        return false;
    }
}

This page has some custom logic that checks if a user can obtain access to certain action in the controller. In your real application, you will probably create some kind of custom code that reads these rules from the configuration files or database, but for demo purposes, this will be good enough. In this code, the IP address is passed to the class but not used in the rules (although it can be easily added if needed).

Creating custom action filters

Custom action filters are simple classes derived from the System.Web.MVC.FilterAttribute class which implement one of the following interfaces:

  • IAuthorizationFilter - if you want to authorize page access
  • IActionFilter - if you wan to attach handlers before and after come action is called
  • IResultsFilter - if you want to attach handlers before or after some result (view) is generated
  • IExceptionFilter - if you want to handle exceptions that occur in actions

You can see more details about the usage of these custom filters in Creating Custom Action Filters in ASP.NET MVC- CodeGuru. I'm implementing a custom authorization rule so I will create a filter that implements the IAuthorizationFilter interface. This filter is shown in the following code:

C#
public class SecurityFilter : FilterAttribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationContext filterContext)
    {
        HttpCookie authCookie = 
          filterContext.HttpContext.Request.Cookies[FormsAuthentication.FormsCookieName];

        if (authCookie != null)
        {
            FormsAuthenticationTicket authTicket = 
                   FormsAuthentication.Decrypt(authCookie.Value);
            var identity = new GenericIdentity(authTicket.Name, "Forms");
            var principal = new GenericPrincipal(identity, new string[]{ authTicket.UserData });
            filterContext.HttpContext.User = principal;
        }

        var Controller = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
        var Action = filterContext.ActionDescriptor.ActionName;
        var User = filterContext.HttpContext.User;
        var IP = filterContext.HttpContext.Request.UserHostAddress;

        var isAccessAllowed = PageAccessManager.IsAccessAllowed(Controller, Action, User, IP);
        if (!isAccessAllowed)
        {
            FormsAuthentication.RedirectToLoginPage();
        }
    }
}

This filter takes information from the authentication cookie that is populated in the login action described above and puts this information in the User object in the HttpContext. Then, information about the controller, action, and IP address are taken from the context and passed to the PageSecurityManager class that will determine if the access is to be allowed to the user.

If access to the controller's action is not allowed, the user will be redirected to the login page (the one that is set in the web.config file - see code above). The FormsAuthentication.RedirectToLoginPage method is useful not only because it automatically reads the login page from the config, but also because it puts the current URL as a ReturnURL parameter that is sent to the login page. This fits in to the logic of the login action described above - if the current page is passed as a ReturnURL, the login controller will redirect the request to the current page if login is successful.

This filter can be applied on the action in one of three ways described above:

  1. Adding the filter to an individual action
  2. Adding the filter to an individual controller
  3. Setting the filter globally

In this example, I will add this filter globally as shown in the following code example:

C#
public class MvcApplication : System.Web.HttpApplication
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new SecurityFilter());
    }
}

Using this code, my custom security filter is applied on all actions in the application. If the page access manager does not allow access to a certain controller's action, the login page will be shown.

Handling security errors

The code shown above works fine if you want to deny access to pages using one page access manager class. This way, the page/action will never be called at all. However, in your application, there are other components that can deny access to the user. As an example, some code can throw security error even if the current role has access to the page. In the code sample that you can download in this article, /Administrator/DenyAccess simulates this situation - if you are an administrator, you will be able to call this URL after the login, but it will immediately throw a SecurityException. In that case, some "access denied page" should be shown to the user.

I will handle this situation in the same filter by implementing the IExceptionFilter interface where I will implement a method that will handle the exception, if one is thrown. This code will check if the thrown exception is a security exception, and if is, it will redirect the user to a security error page. Additional code is shown in the following listing (code for the OnAuthorization method is omitted because it is shown above):

C#
public class SecurityFilter : FilterAttribute, IAuthorizationFilter, IExceptionFilter
{
    public void OnAuthorization(AuthorizationContext filterContext)
    {
        ...
    }

    public void OnException(ExceptionContext filterContext)
    {

        if (filterContext.Exception != null && 
              filterContext.Exception is System.Security.SecurityException)
        {
            var result = new ViewResult();
            result.ViewName = "SecurityError";
            filterContext.Result = result;
            filterContext.ExceptionHandled = true;
        }
    }

As you can see, this code receives the exception that is thrown by the action, checks if it is a SecurityException, and if it is, it shows the SecurityError view to the page. The important line is the part where the ExceptionHandled property of filterContext is set to true - this line tells the context that the exception is successfully handled by this filter and that it should not propagate further.

Using this filter, I have managed security exception on one place and applied this to all actions in the application.

Alternative approach - Inheriting the Authorization attribute

An alternative for filter implementation is creating a custom authorization attribute. In order to create a custom authorization attribute, you will need to extend the AuthorizationAttribute class and implement AuthorizeCore that should return true if the page is authorized and HandleUnauthorizedRequest that is called when an unauthorized request should be processed, as shown in the following example:

C#
public class CustomSecurityAttribute : AuthorizeAttribute
{

    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {           
        return base.AuthorizeCore(httpContext);
    }

    protected override void HandleUnauthorizedRequest(AuthorizationContext context)
    {
        base.HandleUnauthorizedRequest(context);
    }
}

In the overridden method, you will need to add code similar to the code above and return true/false if the user has access. Code in the methods is similar to code in the custom filter so you can use any of these approaches, but it would be best to apply it as a global filter in the Global.asax.cs file:

C#
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    filters.Add(new CustomSecurityAttribute());
}

In the sample code that you can download in the article, this attribute is not implemented because it is more-less a copy of the previous code. Also, this line of code is commented out in the Global.asax.cs file.

Using third party libraries for page access control

When you implement page access security, you can use some of the existing third party libraries. I will show you how you can implement page access security using the Fluent Security library.

With the Fluent Security library, you can programmatically set page access rules to individual controllers/action. The following code sample shows how you can apply Fluent Security policies to controllers and actions.

C#
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    SecurityConfigurator.Configure(configuration =>
    {
        // Let Fluent Security know how to get the authentication status of the current user
        // And let Fluent Security know how to get the roles for the current user

        // This is where you set up the policies you want Fluent Security to enforce
    });
        //Add configured attribute as a global filter
    GlobalFilters.Filters.Add(new HandleSecurityAttribute(), 0);
}

In order to apply Fluent Security policies, you will need to attach HandleSecurityAttribute as a global filter in the RegisterGlobalFilters method in the Global.asax.cs file. When you attach an attribute, you will need to configure it first, which includes:

  1. Definition of method that will tell Fluent Security how to determine user roles.
  2. Definition of security policies that will be applied to controllers and actions.

In the first part of the configuration code, you will need to define how the Fluent Security library will take information about the user (e.g., is the user authenticated and what are his roles). This part of code is shown in the following listing:

C#
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    SecurityConfigurator.Configure(configuration =>
    {
        // Let Fluent Security know how to get the authentication status of the current user
        configuration.GetAuthenticationStatusFrom(() => 
               HttpContext.Current.User.Identity.IsAuthenticated);

        // Let Fluent Security know how to get the roles for the current user
        configuration.GetRolesFrom(() =>
        {
            var authCookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];

            if (authCookie != null)
            {
                var authTicket = FormsAuthentication.Decrypt(authCookie.Value);
                return authTicket.UserData.Split(',');
            }
            else
            {
                return new[]{""};
            }
        });

        // This is where you set up the policies you want Fluent Security to enforce
    });
        //Add configured attribute as a global filter
    GlobalFilters.Filters.Add(new HandleSecurityAttribute(), 0);
}

The code defines that Fluent Security will determine if the user is authenticated and where it will find an array of user roles (from the authentication cookie in this case). Code that is used to configure access policies is shown in the following example.

The part of the configuration code that applies security policies to controllers is shown in the following listing:

C#
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    SecurityConfigurator.Configure(configuration =>
    {
        // Let Fluent Security know how to get the authentication status of the current user
        // Let Fluent Security know how to get the roles for the current user

        // This is where you set up the policies you want Fluent Security to enforce
        configuration.For<PublicController>().Ignore();

        configuration.For<PublicController>(x => x.Dashboard()).RequireRole("Public");

        configuration.For<RegisteredController>(x => x.Index()).DenyAnonymousAccess();
        configuration.For<RegisteredController>(x => x.Dashboard()).RequireRole("Registered", "Admin");
        configuration.For<RegisteredController>(x => x.Home()).RequireRole("Registered", "Admin");
        configuration.For<RegisteredController>(x => x.MyAge()).RequireRole("Registered", "Admin");

        configuration.For<AdminController>(x => x.Index()).DenyAnonymousAccess();
        configuration.For<AdminController>(x => x.Dashboard()).RequireRole("Admin");
        configuration.For<AdminController>(x => x.Home()).RequireRole("Admin");
        configuration.For<AdminController>(x => x.Denied()).RequireRole("Admin");
    });
        //Add configured attribute as a global filter
    GlobalFilters.Filters.Add(new HandleSecurityAttribute(), 0);
}

Code for initialization of user roles that is explained in the previous example in not shown here. This part of the configuration code defines that rules should not be applied to the public controller except on the dashboard action where only a user with a "Public" role can access. On all index actions, anonymous access is denied. In order to access actions in the registered controller, the user needs to have a "Registered" or "Admin" role, and to access Admin actions, he would need to have an "Admin" role.

Note that in the code sample that can be downloaded, this attribute is commented out because custom code access security is active but you can easily comment out the custom security filter and add this attribute.

There is a number of policies that can be added to the controllers/actions such as:

  1. DenyAnonymousAccess - The user must be authenticated. Requires no specific role.
  2. DenyAuthenticatedAccess - The user must be anonymous.
  3. RequireRole - The user must be authenticated with one or more of the specified roles.
  4. RequireAllRoles - The user must be authenticated with all of the specified roles.
  5. Ignore - All users are allowed.

See more details on the Fluent Security website.

Control-level protection

Sometimes you will need to create a more fine-grained permission policy where you need to allow users to see particular sections on the page. In that case, you will allow the user to access some page, but in the view, you will need to control whether some part of the page should or should not be shown. The easiest way is to simply surround parts of the pages that should be protected directly in the view. Something like the following code:

ASP.NET
@if (User.IsInRole("Admin"))
{
    <h2>Admin pages</h2>
    <a href="/Admin/Index">Index</a>
    <a href="/Admin/Home">Home</a>
    <a href="/Admin/Dashboard">Dashboard</a>
}
@if (User.IsInRole("Registered"))
{
    @Html.RenderPartial("SignOut");
}

@if (User.IsInRole("Public"))
{
    @Html.Action("Ad", "Public");
}

I believe that this solution is simple enough and that you will not need any additional frameworks for control access. It is simple enough and do not require any dirty coding - you can surround any part of a page, partial view, or call to action with the appropriate condition. You can put this condition inside the partial view if you do not want to repeat it around each call.

Control level protection using security filters

As an alternative, you can use filters to control whether the controller action should display results. You can use the same filters as the ones that control page access security (described above). An example of the filter that blocks output if the user does not have permission to access an action is shown in the following listing:

C#
using System.Web.Mvc;

public class SecurityFilter : FilterAttribute, IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
    }

    public void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if (filterContext.ActionDescriptor.ControllerDescriptor.ControllerName == "Admin"
            && filterContext.ActionDescriptor.ActionName == "Home"
            && User.IsInRole("Registered"))
            filterContext.Result = null;
    }
}

In the example above, I have created a security filter that implements the IActionFilter interface. This filter will be called before and after each action method call. After an action is executed, I check if the registered user tried to open the Admin/Home page. If so, this filter will set the result that is returned in the view to a null value and nothing will be generated as output.

Another way to implement security checks it to implement IResultFilter and define security check rules in the OnResultExecuting method. An example is shown in the following listing:

C#
using System.Web.Mvc;

public class SecurityFilter : FilterAttribute, IResultFilter
{
    public void OnResultExecuting(ResultExecutingContext filterContext)
    {
        var viewResult = filterContext.Result as ViewResult;
        var controller = filterContext.RouteData.Values["controller"];
        var action = filterContext.RouteData.Values["action"];
        var view = viewResult.ViewName;
        if (controller == "Admin" && 
            action == "Index" &&
            view == "Blocked")
            filterContext.Cancel = true;
    }

    public void OnResultExecuted(ResultExecutedContext filterContext)
    {
    }
}

The controller action returns an ActionResult that will be executed to generate output. In this case, the OnResultExecuting method will be called before the ActionResult object is called to render the view. In this method, you can take the names of the controller, action, and view, and decide whether the execution result should be canceled or not.

Both of these methods will block both direct calls, AJAX calls, and @Html.Action method calls, but will not work with the @Html.RenderPartial method because this method does not call a new action. The best way to protect partial views is to set some logic conditions in the view.

Code Access Security

Page access security ensures that the user cannot open any restricted page if he does not have enough permissions. However, in some cases you will need to implement security constraints in the code, where you will need to check whether the user who can open some page can access some code, data, or other resources. As an example, you will need to check whether a user can open or write a file in some folder. In this section, I will make no difference between code and data access security, because I will assume that when we are protecting data, we are protecting code that accesses the data. Hence, in this overview, there is no difference between code access security and data access security - if you want to secure data, you secure the code that accesses the data.

The standard way to implement code/data access security is to implement custom rules in the code that reads/writes data. However, there is another alternative - to use .NET security permissions. Here I will show a brief overview of security, but you can find more details in Understanding .NET Code Access Security.

Using standard security permissions

.NET Framework provides a mechanism for demanding permissions to access code. There are a number of different built-in permissions you can use directly in your code such as:

  1. FileIOPermission that enables you to demand that the current user has right to access some file or folder (e.g., read or write permission),
  2. PrincipalPermission that enables you to demand that the current user has some name or role.

There are two ways to perform security checks - declarative and imperative.

In declarative code access security, you can add an attribute to some method - an example is shown below:

C#
[PrincipalPermissionAttribute(SecurityAction.Demand, Role = "Admin")]
public static void Method()
{
}

In this example, it is demanded that the current user has the role "Admin" - otherwise a SecurityException will be thrown. Note that declarative security checks are hardcoded. Once compiled, you cannot change rules.

In the imperative manner, you put security demands directly in code. An example is shown in the following listing:

C#
public static void SendFile(string file)
{   
    var f = new FileIOPermission(FileIOPermissionAccess.Read, "C:\\test_r");
    f.AddPathList(FileIOPermissionAccess.Write | FileIOPermissionAccess.Read, "C:\\example\\out.txt");
    if(file!=null)
    {
        f.Demand();
    }
}

The imperative method is more flexible because you can create complex permissions, combine them, and optionally call demand actions.

Using custom permissions

If you need to use some custom permission, you can create your own permission rules. In that case, you should create your own permission class - see more details about creating these classes in the Implementing a Custom Permission article. In shot, everything you need to do is:

  1. Create a class that extends the CodeAccessPermission class and implements the IUnrestrictedPermission interface.
  2. Mark the class as serializable.
  3. Override/define methods for copying, intersecting, and determining if a permission object is a subset of the current permission object.
  4. Implement methods for serialization from and to XML.
  5. Implement a method that will determine if permission is unrestricted.
  6. Optionally you can define a custom security attribute class in order to use declarative security - see the Adding Declarative Security Support article for more details.

An example of such a class is shown in the following listing - you can find a complete example in the Implementing a Custom Permission article.

C#
[Serializable()]
public sealed class CustomPermission : CodeAccessPermission, IUnrestrictedPermission
{
    public CustomPermission(PermissionState state)
    {
    }

    public bool IsUnrestricted()
    {
    }

    public override IPermission Copy()
    {
    }

    public override IPermission Intersect(IPermission target)
    {
    }

    public override bool IsSubsetOf(IPermission target)
    {
        
    }

    public override void FromXml(SecurityElement PassedElement)
    {
        //Get the unrestricted value from the XML and initialize 
        //the current instance of unrestricted to that value.
    }

    public override SecurityElement ToXml()
    {
        //Encode the current permission to XML using the 
        //SecurityElement class.
    }
}

Once you have defined this class, you can control code access, as you can see in the following example.

If you want some code to be protected, you will need to demand that permission in the code. Demanding code in the imperative manner looks like in the following example.

C#
public class Service
{
    public static void Method1()
    {
        Method2();
    }

    public static void Method2()
    {
        Method3();
    }

    public static void Method3()
    {
        RequireCustomPermission();
    }

    public static void RequireCustomPermission()
    {
        var cp = new CustomPermission(PermissionState.Unrestricted);
        cp.Demand();
    }
}

In the RequireCustomPermission() method, a new instance of custom permission is created and it is demanded that a calling code has this custom permission. The Demand() call will go through all methods that have called this method (Method3, Method2, and Method1) and checks if any of them has this custom permission enabled or disabled.

If you wan to prevent some caller from executing the RequireCustomPermission() method, you will need to instate this permission and disallow it. The following code shows how you can deny permission in some caller:

C#
public string Protected()
{
    var cp = new CustomPermission(PermissionState.None);
    cp.PermitOnly();
    Service.Method1();
    return "ok";
}

This code creates a custom permission object with a None permission and defines that this permission is permitted. In other words, no access to the resource is set in this code - see more details about the PermitOnly method here. When this code, that has no permission to access resources, calls method Method1, which calls Method2, Method3, and the RequireCustomPermission method, once the Demand method is called, a SecurityException will be thrown, and your code in RequireCustomPermission will be protected. If this method is part of some controller, the following exception will be thrown:

Image 4

As you can see, the security exception is thrown in the .Demand() call. In the stack trace, you can see the full path of the methods that are called.

There was another method in the .NET security package - Deny. However, this method is deprecated in .NET Framework 4.0 and calling this method will cause a runtime error.

If you are interested in more details, you might read some other articles that describe this in more details such as Understanding .NET Code Access Security. Code Access Security is a very wide area, so I cannot cover everything in one section. Therefore, it would be better to take a look at existing articles where this is already explained in more detail.

Forcing the HTTPS protocol

In order to protect sensitive data, the best practice is to use HTTPS instead of HTTP. The HTTPS protocol will automatically encrypt sensitive information that is posted from the client to the server, so nobody can use network sniffers to intercept and read these information.

In MVC, the [RequireHttps] attribute can be used to force HTTPS. If you put this attribute on a particular action, it will redirect the request to the https://SITE/Controller/Action URL if it is called with http://SITE/Controller/Action. This is useful if you want to make sure that some actions must be executed via the HTTPS protocol instead of plain HTTP. An example is shown below:

C#
public class PublicController : Controller
{
    [RequireHttps]
    public string Login()
    {
        return View();
    }
}

If you try to call the login page using http://..../Public/Login, you will be immediately redirected to the HTPS version of the page https://..../Public/Login where you can enter the login details.

Note that HTTPS needs to be configured on IIS in order to make this work (it is not enabled by default).

You can also protect your form authentication mechanism by setting that SSL is required for passing an authentication cookie. This is shown in the following example:

XML
<configuration>
   <system.web>
   <authentication mode="Forms">
      <forms requireSSL="true" loginUrl="login.aspx"> 
      </forms>
   </authentication>
   </system.web>
</configuration>

If you set the requireSSL attribute to true, an authentication cookie will be encrypted using the standard SSL protocol. Se more details about this attribute in the forms Element for authentication (ASP.NET Settings Schema) article.

If your cookies contain sensitive information, you can require that they are transferred via secure socket layer. You will need to set the requireSSL attribute in the web.config as shown in the following example:

XML
<configuration>
    <system.web>
        <httpCookies requireSSL="true" />
    </system.web> 
</configuration>

See more details about this setting on the MSDN httpCookies Element (ASP.NET Settings Schema) article.

Cross-site request forgery

Cross-site request forgery attack is a kind of attack where an external site/script exploits the user identity information stored in the browser (e.g., cookies).

Let us assume that you are accessing your bank site and at the same time you are browsing other sites (e.g., in another tab). You are logged in to your bank's site, authenticated, and your authentication information is placed in browser cookies. At the same time you have opened some page on another site www.evil.com in a second tab. In that case, you will be accessing two servers at the same time as shown in the following figure:

ASPNET-MVC-Security/CSRF.PNG

Imagine that you have opened some page on the second site (www.evil.com) where is placed some form with an action attribute that points to your bank site and that has some script that immediately submits the form. Once you load this page from the evil site, it will post a request from your browser to your bank site. This request will take all authentication cookies from the browser and your bank server will not know whether this call is performed via the first tab (regular usage) or second tab (request forgery attack), so it will authenticate this malicious request and accept it. In this case, the script might clean your account.

Preventing cross-site request forgery attacks

As an example, I have added a dummy link on a page that posts request to the /Registered/MyAge action and sets the age to 0.

JavaScript
<a id="CSFR">Click me to reset your age!!!!</a>

<script type="text/javascript">
    $("#CSFR").click(function () {
        $.ajax({
            url: "/Registered/MyAge",
            data: { age: "0" },
            type: 'POST',
            success: function () { alert("Attack succeeded");  },
            error: function () { alert("Attack failed"); }
        });
    });
</script>

If you click on the link, your age will be reset to 0 although you didn't want this, because this request sends cookies to the server. The same thing would happen if this script is on another site that you have opened in another tab (however, in that case, the url parameter in the AJAX code will need to have a direct URL to your application). MVC enables you to protect your pages from CSRF attacks by adding a token on the page that will be checked on the server-side. An example of a token that is added to the MyAge view is shown in the following code:

ASP.NET
<form action="/Registered/MyAge" method="post">
  @Html.AntiForgeryToken()
  My Age:<input type="text" name="age" value="@Model" />
  <input type="submit" name="Save" value="Save" />
</form>

In the controller, we should add the [ValidateAntiForgeryTokenAttribute] attribute that checks whether the anti-forgery token exists in the request, as shown in the following example:

C#
[ValidateAntiForgeryTokenAttribute]
public ActionResult MyAge(string age)
{
    Session["age"] = age;
    return View(Session["age"]);
}

If token is not sent in the request, the call will fail. If you try to click on the link, you will get an error message. However, if you try to remove this token and attribute, the AJAX call will succeed and your age will be reset.

Preventing cross-site scripting

Cross site scripting occurs when someone enters potentially dangerous tags that execute some JavaScript. An example of a potentially dangerous script that can be injected in a page is shown in the following listing:

HTML
<script>alert("hello");</script>

Instead of the simple alert here, any other script that takes your cookies can be added and the cookies sent to some third party site, some ads injected in the page, etc. MVC enables automatic protection of your code against XSS attacks using the following rules:

  1. Any content that is placed on pages is automatically HTML encoded so if you have some potentially dangerous script, it will be shown as text.
  2. If a request contains a potentially dangerous script in any parameter, an error will be returned.

However in most cases, you will need to turn off this default validation. Examples are:

  1. In a CMS system, raw HTML is placed in the database and it should be shown to the user as HTML because it contains a different formatting.
  2. If you are using an HTML WYSIWYG editor (e.g., TinyMCE, XStandard) that enables the user to format text, you will need to disable HTML validation because you know that you want to receive HTML from the browser.

In the following example, I will show you what could happen if you turn-off default validation and how you can implement a trade-off.

XSS vulnerable code - An example

In this section, I will show you an example of XSS vulnerable code. Let us assume that we need to create a page that enables the user to enter his profile. Also, I will assume that the profile has a rich format so it needs to allow HTML formatting. To implement this, we will need to create the two following actions:

C#
public class RegisteredController : Controller
{
    [HttpGet]
    public ActionResult Profile()
    {
        var age = Convert.ToString(Session["profile"]);
        return View(Session["profile"]);
    }

    [HttpPost]
    [ValidateInput(false)]
    public ActionResult Profile(string profile)
    {
        Session["profile"] = profile;
        return View(Session["profile"]);
    }
}

The GET action shows the user profile from the session, and the POST action writes it back to the session. Note that I have to place the ValidateInput(false) attribute in order to prevent the MVC framework from throwing a "Potentially dangerous request" exception when it finds the HTML code in the profile.

The View for this action is shown in the following listing:

ASP.NET
@model string

<h2>Profile</h2>
<p>@Html.Raw(Model)</p>

<h3>Update profile</h3>
<form action="/Registered/Profile" method="post">
    Profile:<textarea id="profile" name="profile" rows="10" cols="40">@Model</textarea>
    <input type="submit" name="Save" value="Save" />
</form>

This view displays the profile as HTML and allows the user to update the profile (in a real example, I would use the TinyMCE editor instead of a plain text area). Note that I need to place Html.Raw in order to prevent MVC to automatically encode model content. Html.Raw will display it as raw HTML code.

Setting ValidateInput(false) makes this code vulnerable to XSS attacks. If the user enters something like the following text:

HTML
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"
        type="text/javascript"></script>
<style type="text/css">
.red { color: red }
</style>
<hr />
<h2 id="title">Hello</h2>
<input type="text" name="keyword" id="keyword" data-rel="Custom"/>
<p style="border:solid">This is some <b>Profile</b>  <em>text</em>. <br/>
Validate Input <u>must be disabled</u>
<img src="http://i.msdn.microsoft.com/Areas/Sto/Content/Images/ShareThis/email.gif" />
in order to <span class="red">display</span> formatted text.</p>
<img alt="Hello" src="javascript:alert('Hello from image source')"  
        onload="javascript:alert('Hello after load')"
        onclick="javascript:alert('Hello after click')"/>
<script>alert("hello");</script>
<hr/>

You will see an alert once you reload the view because there is a potentially dangerous JavaScript that has passed .NET validation. Now we have disabled strict .NET validation, but we have opened the door for various script attacks. Although the input cannot be validated in order to accept HTML code, there should be some validation that prevents dangerous code.

Anti-XSS Library

Microsoft's Anti-XSS library will enable you to clean posted HTML code with more intelligent rules than rejecting a request that has any HTML tag. You can download the Anti-XSS library from here, and XSS libraries are included in the code sample.

If you want to clean HTML input taken from the user, you can use the GetSafeHtmlFragment method of the Sanitizer class (in version 3.1, this method is placed in the AntiXSS class, but it has moved since 4.0):

C#
[HttpPost]
[ValidateInput(false)]
public ActionResult Profile(string profile)
{
      Session["profile"] = Sanitizer.GetSafeHtmlFragment(profile);
      return View(Session["profile"]);
}

This call will remove potentially dangerous tags (e.g., <script>) from the input text and leave just the safe ones. If you want to store the original HTML in the database or you cannot control the input, another solution might be to encode it directly in the view:

ASP.NET
@model string

<h2>Profile</h2>
<p>@Html.Raw(Sanitizer.GetSafeHtmlFragment(Model))</p>

<h3>Update profile</h3>
<form action="/Registered/Profile" method="post">
    Profile:<textarea id="profile" name="profile" 
    rows="10" cols="40">@Model</textarea>
  <input type="submit" name="Save" value="Save" />
</form>

In this example, unsafe HTML is encoded in plain text, but left as original in the text area so the user can change it. The GetSafeHtmlFragment method will make the following changes to the HTML:

  1. Any script tags will be removed (both references and inline scripts)
  2. Inline CSS styles will be wrapped with <!-- and -->
  3. Inline CSS classes and values for the ID, class, and name attributes will get the prefix x_
  4. Custom attributes will be removed (e.g., data-rel)
  5. Potentially dangerous attributes will be removed (onload, onclick in the image tag)

When you get safe HTML using the Sanitizer class from the dangerous HTML source shown in the previous section, you will get the following result:

HTML
<style type="text/css">
<!--
.x_red
    {color:red}
-->
</style>
<div>
<hr>
<h2 id="x_title">Hello</h2>
<input type="text" name="x_keyword" id="x_keyword">
<p style="border:solid">This is some <b>Profile</b> <em>text</em>. <br>
Validate Input <u>must be disabled</u> 
  <img src="http://i.msdn.microsoft.com/Areas/Sto/Content/Images/ShareThis/email.gif"> in order to
<span class="x_red">display</span> formatted text.</p>
<img alt="Hello" src="">
<hr>
</div>

As you can see, the HTML is safe now - all dangerous code has been removed. However, the code is a little bit messed up because the Sanitizer class modifies inline classes and values for name, ID, and class attributes by adding the prefix "x_" (at least in this version). This is reported as an issue on the Anti-XSS site but it has still not been resolved. If this is a problem to you, you will need to manually remove prefixes - I use the following code:

C#
Session["profile"] = Sanitizer.GetSafeHtmlFragment(profile)
                    .Replace("=\"x_","=\"")
                    .Replace(".x_", ".");

I have replaced each ="x_ occurrence with =" to revert the fix made by Sanitizer in attributes, and any occurrence of .x_ with . in order to revert unwanted fixes in classes. The only remaining inconvenience is that custom HTML5 attributes such as data-rel, data-text are removed and cannot be reverted. I hope that this will be fixed in some future release of Sanitizer.

If you will use the AntiXSS library, here are a few tips for you:

  1. Do not call Sanitizer.GetSafeHtmlFragment() directly - create your own class that will wrap it. GetSafeHtmlMethod is moved from the AntiXSS class in version 3.1. to the Sanitizer class in 4.0 so it would be better to wrap this in your code and end up with less changes.
  2. If you are using .NET 4, you can replace the default encoder with the custom one that will automatically sanitize the output. This is already covered in the Using AntiXss as the Default Encoder for ASP.NET article.

Conclusion

In this article, I have shown some basic security elements in MVC applications such as:

  1. User authentication
  2. Page access control
  3. Script attack prevention
  4. Cross-site request forgery prevention

I believe that you can easily modify this code and apply it in your applications. Note that code in the sample is not 100% identical to the examples shown in the article. You will need to switch on/off some parts of code, but I believe that you will be able to adjust it easily and run these examples. As an example, the current code has turned on custom filter security and commented out Fluent security code, but you will be able to change this.

If you have any ideas about security that could be included here, let me know and I will update this article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Program Manager Microsoft
Serbia Serbia
Graduated from Faculty of Electrical Engineering, Department of Computer Techniques and Informatics, University of Belgrade, Serbia.
Currently working in Microsoft as Program Manager on SQL Server product.
Member of JQuery community - created few popular plugins (four popular JQuery DataTables add-ins and loadJSON template engine).
Interests: Web and databases, Software engineering process(estimation and standardization), mobile and business intelligence platforms.

Comments and Discussions

 
SuggestionFanatabolus Pin
Zia Ullah Khan11-Dec-11 21:52
professionalZia Ullah Khan11-Dec-11 21:52 
GeneralMy vote of 5 Pin
Zia Ullah Khan11-Dec-11 21:49
professionalZia Ullah Khan11-Dec-11 21:49 
GeneralMy vote of 5 Pin
member6011-Dec-11 18:46
member6011-Dec-11 18:46 
Questionvote '5' Pin
shaheen_mix7-Dec-11 0:39
shaheen_mix7-Dec-11 0:39 
GeneralMy vote of 5 Pin
Monjurul Habib3-Dec-11 0:39
professionalMonjurul Habib3-Dec-11 0:39 
GeneralMy vote of 5 Pin
Prasanth S1-Dec-11 17:06
Prasanth S1-Dec-11 17:06 
QuestionGreat article Pin
Sacha Barber30-Nov-11 23:49
Sacha Barber30-Nov-11 23:49 
GeneralMy vote of 5 Pin
andrew458229-Nov-11 7:53
andrew458229-Nov-11 7:53 
GeneralMore security requirements Pin
Jarmo Muukka29-Nov-11 2:35
Jarmo Muukka29-Nov-11 2:35 
GeneralRe: More seurity requirements Pin
Jovan Popovic(MSFT)29-Nov-11 13:49
Jovan Popovic(MSFT)29-Nov-11 13:49 
GeneralFluent Security Pin
kristofferahl24-Nov-11 23:26
kristofferahl24-Nov-11 23:26 
QuestionI see you've implemented my suggestion? Pin
Simon_Whitehead24-Nov-11 14:33
Simon_Whitehead24-Nov-11 14:33 
GeneralRe: I see you've implemented my suggestion? Pin
Jovan Popovic(MSFT)24-Nov-11 23:16
Jovan Popovic(MSFT)24-Nov-11 23:16 
GeneralRe: I see you've implemented my suggestion? Pin
Simon_Whitehead27-Nov-11 13:46
Simon_Whitehead27-Nov-11 13:46 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.