Securing an ASP.NET MVC Application Using FluentSecurity

The excellent class library FluentSecurity allows you to easily secure an ASP.NET MVC application. By using it, you can benefit from the following main advantages:

  1. Authorization rules can be specified for both single action methods or entire controllers.
  2. Security is specified in a centralized place in a readable and maintainable way, thus making it unnecessary to decorate your controllers with [Authorize] attributes cluttering your code.
  3. FluentSecurity forces you by default to explicitly specify authorization rules for all action methods to prevent you from forgetting to secure them.
  4. You can extend the library and implement your own authorization rules and violation handlers.
  5. The specified security configuration is unit testable; thus, you can verify that it is working correctly.

Integration into an MVC Application

The quickest way to integrate FluentSecurity into an ASP.NET MVC application is installing the NuGet package FluentSecurity, so open up the NuGet Package Manager Console, make sure your MVC project is selected in the Default project dropdown list, and run the following command:

install-package FluentSecurity

Your project now references the assemly FluentSecurity — besides that, nothing has been changed.

Retrieving the User's Authentication Status

To let FluentSecurity handle authorization globally within your application, open the Global.asax file and add the HandleSecurityAttribute to the global filter collection within the RegisterGlobalFilters method (make sure that you have imported the namespace FluentSecurity):

filters.Add(new HandleSecurityAttribute(), 0);

It is important to set the attribute's filter run order to 0 so that FluentSecurity can enforce security rules before anything else in the request pipeline is executed. Furthermore, add the following security configuration shown below to the Application_Start method before the RegisterGlobalFilters method is called — otherwise, FluentSecurity will throw an exception stating that no security rules have been specified:

SecurityConfigurator.Configure(configuration =>
{
    // Tell FluentSecurity where to obtain the user authentication status from
    configuration.GetAuthenticationStatusFrom(() =>
        HttpContext.Current.User.Identity.IsAuthenticated);
});

The above example instructs FluentSecurity to call the specified Func<bool> delegate – which is querying the HttpContext.User.Identity.IsAuthenticated property used by ASP.NET Forms Authentication – to retrieve the current user's authentication status.

Note that, when you run the application, you will receive a ConfigurationErrorsException — this is by design! By default, FluentSecurity throws that exception whenever an action method for which there is no security explicitly specified is called. If you don't like this feature, you can easily turn it off:

configuration.IgnoreMissingConfiguration();

However, I strongly recommend not to ignore missing configurations, for the thrown exception prevents you from forgetting to secure action methods (or controllers) by accident.

Specifying Security Policies

So far, we have configured authentication information, but we haven't specified any authorization rules yet. FluentSecurity uses the concept of Policies to configure authorization rules for either entire controllers or single action methods.

To secure your HomeController from unauthenticated access, add the following line to the configuration:

configuration.For<HomeController>().DenyAnonymousAccess();

The DenyAnonymousAccess extension method registers the DenyAnonymousAccessPolicy for the entire HomeController. If an unauthenticated user attempts to call any action methods living inside the controller, a PolicyViolationException is thrown. An authenticated user, on the other hand, will pass.

You can also add the same policy to all controllers in your application:

// Secure all action methods of all controllers
configuration.ForAllControllers().DenyAnonymousAccess();
 
// Make sure that users can still log on
configuration.For<AccountController>(ac => ac.LogOn()).Ignore();

The lambda expression ac => ac.LogOn() restricts the IgnorePolicy to the LogOn action method. At that point of time, only parameterless methods can be selected, but a future release of FluentSecurity is likely to include support for parametrized methods.

In the current version 1.4.0, the following policies are available out of the box:

  • DelegatePolicy — The specified delegate must return true or a success result.
  • DenyAnonymousAccessPolicy — The user must be authenticated.
  • DenyAuthenticatedAccessPolicy — The user must be anonymous.
  • IgnorePolicy — All users are allowed.
  • RequireAllRolesPolicy — The user must be authenticated with all of the specified roles.
  • RequireRolePolicy — The user must be authenticated with at least one of the specified roles.

Implementing a Custom Policy

If none of the existing policies fulfills your needs, you can create your own policy by implementing the ISecurityPolicy interface and with it its Enforce method. The following example shows the implementation of a custom policy that restricts access to a controller to requests on weekends:

public class WeekendsOnlyPolicy : ISecurityPolicy
{
    public PolicyResult Enforce(ISecurityContext context)
    {
        DateTime now = DateTime.Now;
        bool isWeekend = now.DayOfWeek == DayOfWeek.Saturday
            || now.DayOfWeek == DayOfWeek.Sunday;

        return isWeekend
            ? PolicyResult.CreateSuccessResult(this)
            : PolicyResult.CreateFailureResult(this, "Access denied!");
    }
}

Handling Policy Violations

When a policy is violated, FluentSecurity will throw a PolicyViolationException. You can, of course, catch the exception regularly and do with it whatever you like. However, the cleaner approach would be to register a Policy violation handler which has to meet certain criteria:

  • It has to implement the IPolicyViolationHandler interface (a single Handle method accepting a PolicyViolationException and returning an ActionResult).
  • The handler name has to match the format <PolicyName>ViolationHandler, since FluentSecurity uses a naming convention to locate the correct policy violation handler.

The recommended way to register custom policy violation handlers is by using an IoC container. Please refer to the documentation page for more information on how to create and register policy violation handlers using a dependency injection framework.

Testing Your Security Configuration

To ensure that your security rules are configured correctly, you can test them in a very readable, fluent way using the NuGet package FluentSecurity.TestHelper:

install-package FluentSecurity.TestHelper

Given that you have encapsulated the security configuration in the ConfigureFluentSecurity method of a separate Bootstrapper class, possible expectations for the security configuration created before could look like the following:

// Arrange
Bootstrapper.ConfigureFluentSecurity();
 
// Act
var results = SecurityConfiguration.Current.Verify(expectations =>
{
    expectations.Expect<HomeController>().Has<DenyAnonymousAccessPolicy>();
    expectations.Expect<AccountController>().Has<DenyAnonymousAccessPolicy>();
    expectations.Expect<AccountController>(ac => ac.LogOn()).Has<IgnorePolicy>();
});
 
// Assert
bool isValidConfiguration = results.Valid();
Assert.IsTrue(isValidConfiguration);

Besides the Has extension method, there is also a DoesNotHave version expecting that a certain policy is not assigned to an action method or a controller. For more information on how to test your security configuration, please have a look at the corresponding documentation page.

Further Resources

If you are interested in reading more about the project or its author, here are some interesting references:

Use the coupon code LAUNCHDAY for $10 off!

Learn ES6

16 Comments

Morten

Hey m8! :) Great post..

Thou i dont get the hang on Custom violation messages? :) Instead of the generic one. What if i would use a page on my own for denied accese etc.

How would i go around that? :)

Parikshita

A nice article....Thanks

Parimal Patel

Hi

Its really good stuff. Thanks for providing good stuff. During implementation i am getting following error

DashboardController' does not contain a definition for 'LogOn' and no extension method 'LogOn' accepting a first argument of type Dashboard.Controllers.DashboardController' could be found (are you missing a using directive or an assembly reference?

I am not getting error on following configuration, means there is not referenceing issues.

configuration.ForAllControllers().DenyAnonymousAccess();

also i have chacked dll version, it is 1.4.0.0.

can you help for it please?

Thanks Parimal

Marius Schulz

Since the IPolicyViolationHandler.Handle method returns an ActionResult, you can easily redirect to a route displaying your custom Access Denied! view when the DenyAnonymousAccessPolicy is violated. Using the RedirectToRouteResult class inheriting from ActionResult, your code could look like the following:

public class DenyAnonymousAccessPolicyViolationHandler : IPolicyViolationHandler
{
    public ActionResult Handle(PolicyViolationException exception)
    {
        return new RedirectToRouteResult(RouteNames.AccessDenied, null);
    }
}

public static class RouteNames
{
    public const string AccessDenied = "AccessDenied";
}

routes.MapRoute(RouteNames.AccessDenied, "AccessDenied",
    new { controller = "Error", action = "AccessDenied" });

For more information on Policy violation handlers, please refer to the documentation page.

Marius Schulz

If you want to ignore all action methods on your DashboardController and there is no LogOn() action method defined, simply remove the lambda expression from the parentheses:

configuration.For<DashboardController>().Ignore();
Sonali Noolkar

Thanks for posting such an excellent and useful Article.

Nick

I really don't like the idea of throwing an exception for a missing security configuration on run. I would prefer to see something similar to what Automapper does, which is to have a method (AssertConfigurationIsValid) that can be called from a Unit Test which checks for mappings, and throws an exception if there are missing mappings.

See here: AutoMapper Configuration validation

Morten

Hey m8 :)

Im trying to do something like this:

SecurityConfigurator
    .Configure(x => x.For<IPolicyViolationHandler>
    .Add<admin.Helpers.DenyAnonymousAccessPolicyViolationHandler>());

namespace admin.Helpers
{
    public class DenyAnonymousAccessPolicyViolationHandler : IPolicyViolationHandler
    {
        public ActionResult Handle(PolicyViolationException exception)
        {
            return new HttpUnauthorizedResult(exception.Message);
        }
    }
}

But dont seem to work :P

Morten

Hey again m8! :) Im close..

public static class RouteNames
{
    public const string AccessDenied = "AccessDenied";
}

public class DenyAnonymousAccessPolicyViolationHandler : IPolicyViolationHandler
{
    public ActionResult Handle(PolicyViolationException exception)
    {
        return new RedirectToRouteResult(RouteNames.AccessDenied, null);
    }
}

public class WebRegistry : Registry
{
    public WebRegistry()
    {
        Scan(scan =>
        {
            scan.TheCallingAssembly();
            scan.AddAllTypesOf<IPolicyViolationHandler>();
        });
    }
}

protected void Application_Start()
{
    SecurityConfigurator.Configure(configuration =>
    {
        configuration.ResolveServicesUsing(type => ObjectFactory.GetAllInstances(type).Cast<object>());
    }
 }

This is the part of the code where i tries to throw a "custom" error. :) But dosnt seem to kick in.

Kristoffer Ahl

@Nick: There is an option to turn that behaviour off but I still think that should be the default since a lot of people are using it without having any unit tests. I've had comments thanking me for putting that in there as it saved them from a lot of headaches.

That being said I think your idea is a good one. I actually have a todo for AssertConfigurationIsValid but it is quite low in priority at the moment. Maybe you can fork the project and send me a pull request? ;O)

If not I'll see if I can get something done for the 2.0 release, but that is still some time away.

Erry

Nice article ! :D Auch wenn ich nicht weiß, worüber du hier zur Hölle referierst :D Aber wie ich bereits sagte, du kommst sicher groß raus !

Viel Glück dabei und ich freue mich auf Mai !

Marlon Assef

Amazing! Working with it and can solve a lot of security issues.

Great !

Michael Wassermann

How do I register the DenyAnonymousAccessPolicy Handler using Unity? I am new to both.

ApiController

Can Fluent Security use with Web API? Demo for ApiController?

Artem

Hi!

How to deny action with parameters for example delete action, I've tried this.

configuration
    .For<CustomerController>(c => c.DeleteConfirmed(0))
    .DenyAuthenticatedAccess().RequireRole(AccessRights.Admin.ToString());

but it doesn't work

Sudhakar

How get the User Authentication status in MVC 5???

I'm using the following code, but its not working

// Tell FluentSecurity where to obtain the user authentication status from configuration.GetAuthenticationStatusFrom(() => HttpContext.Current.User.Identity.IsAuthenticated);