Blog.

Secure your web app fluently

MF

Marco Franssen /

5 min read929 words

Cover Image for Secure your web app fluently

When building a big web application with ASP.NET MVC 3 I ran into a problem to secure my web application in a maintainable way. There are lots of examples with attributes, but this isn't maintainable. So I started searching for other solutions, however most of the information is leaning on those un-maintainable attributes I finally found "Fluent Security".

What does Fluent Security offer you?

Fluent Security provides a fluent interface for configuring security in ASP.NET MVC. No attributes or nasty xml, just pure love. Go get it on NuGet!

What does that mean?

Well it simply means you can bootstrap your security just from your Application_Start() and maintain it on a single place. Besides that you can easily unit test your security setup. So you can test if your security configuration matches the security setup you described in your unit tests. This is a huge advantage because you know for sure the controller actions are secured the way you like it without having the need to click through your complete web application. You can only fail when not defining your tests correctly. That's not all… You can easily extend, modify etc. by adding your own implementations of the interfaces.

Let me start to show you some code so you see for yourself how easy it is! My example is based on a default MVC 3 application and I have added a CategoryController like below to have some extra actions for my example.

public class CategoryController
{
    [HttpGet]
    public ActionResult AddNewCategory()
    {
        return View(new CategoryModel());
    }

    [HttpPost]
    public ActionResult AddNewCategory(CategoryModel model)
    {
        if (!ModelState.IsValid) return View(model);
        //Save the data etc…
        return RedirectToAction("AddNewCategory");
    }

    //Other actions
}

First of all I create a static class for my bootstrap code! I leave the implementation blank because I will first implement some tests.

public static class SecurityBootstrapper
{
    public static void BootUp()
    {
        //Here we will configure our security later on…
    }
}

Then you start to write some tests for your security setup. I would advice you to be very explicit in your test setup although you won't have to. This way you are 100% sure you secured it exactly the way you want and it is 100% transparent. So never write a test for your whole controller, but do it for every specific action.

[TestFixture]
public class FluenSecuritySetupTests
{
    [SetUp]
    public void SetUp()
    {
        BootStrapper.BootUp();
    }

    [Test]
    public void anonymous_access_should_be_allowed_for_the_logon_and_home_index_actions()
    {
        var results = SecurityConfiguration.Current.Verify(expectations =>
        {
            expectations.Expect<UserController>(c => c.LogOn(string.Empty)).Has<IgnorePolicy>();
            expectations.Expect<HomeController>(c => c.Index()).Has<IgnorePolicy>();
        });

        Assert.That(results.Valid(), results.ErrorMessages());
    }

    [Test]
    public void adding_a_new_catogegory_requires_a_system_administrator_role()
    {
        var results = SecurityConfiguration.Current.Verify(expectations =>
        {
            expectations.Expect<CategoryController>(c => c.AddNewCategory()).Has(new RequireRolePolicy(AppRoles.SystemAdministrator));
            expectations.Expect<CategoryController>(c => c.AddNewCategory(null)).Has(new RequireRolePolicy(AppRoles.SystemAdministrator));
        });

        Assert.That(results.Valid(), results.ErrorMessages());
    }
}

After we have defined our tests for our security setup we can implement the actual setup.

public static class SecurityBootstrapper
{
    public static void BootUp()
    {
        SecurityConfigurator.Configure(configuration =>
        {
            configuration.ResolveServicesUsing(type => BootStrapper.Container.ResolveAll(type).Cast<object>());

            configuration.GetAuthenticationStatusFrom(() => HttpContext.Current.User.Identity.IsAuthenticated);
            configuration.GetRolesFrom(Roles.GetRolesForUser);

            configuration.ForAllControllersInAssembly(typeof(HomeController).Assembly).DenyAnonymousAccess();
            configuration.For<HomeController>(c => c.Index()).Ignore();
            configuration.For<UserController>(c => c.LogOn()).Ignore();
            configuration.For<UserController>(c => c.ResetPassword()).RequireRole(AppRoles.UserAdministrator);
            configuration.For<CategoryController>(c => c.AddNewCategory()).RequireRole(AppRoles.SystemAdministrator);
            configuration.For<CategoryController>(c => c.AddNewCategory(null)).RequireRole(AppRoles.SystemAdministrator);
        }
    }
}

As you probably have seen already we configure the security for both the get and post actions. Now we can run our tests to see if we implemented the security like we have defined them in our tests. When all your tests succeeded we are ready to enable it in our web application and define handlers for our policy violations.

public class MvcApplication : HttpApplication
{
    public static IWindsorContainer Container { get; private set; }

    protected void Application_Start()
    {
        SecurityBootstrapper.BootUp();

        Container = new WindsorContainer().Install(FromAssembly.This());

        AreaRegistration.RegisterAllAreas();
        RegisterGlobalFilters(GlobalFilters.Filters);
        RegisterRoutes(RouteTable.Routes);
    }

    //Other members left for brevity…
}

First of all we called our SecurityBootstrapper.BootUp() method in our global.asax to configure our security. Then I used Castle Windsor as dependency container to register my dependencies. Of course you can use your own favorite IoC container. Or plumb your own implementation. In order to install my Fluent Security dependencies I implemented a Windsor installer which takes care of registering the dependencies in the container.

public class WindsorInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(AllTypes.FromThisAssembly().BasedOn(typeof(IPolicyViolationHandler)).Configure(h => h.LifeStyle.Singleton));
    }
}

As you can see I install all PolicyViolationHandlers in my assembly. As an example I have added some implementations for a security policy violation handler.

public class DenyAnonymousAccessPolicyViolationHandler : IPolicyViolationHandler
{
    public ActionResult Handle(PolicyViolationException exception)
    {
        //Log the violation, send mail etc. etc.
        return new HttpUnauthorizedResult(exception.Message);
    }
}

public class RequireRolePolicyViolationHandler : IPolicyViolationHandler
{
    public ActionResult Handle(PolicyViolationException exception)
    {
        //Log the violation, send mail etc. etc.
        var rvd = new RouteValueDictionary(new
        {
            area = "",
            controller = "Error",
            action = "HttpForbidden",
            statusDescription = exception.Message
        });
        return new RedirectToRouteResult(rvd);
    }
}

These violation handlers are mapped by naming convention inside Fluent Security. So a RequireRolePolicy needs a RequireRolePolicyViolationHandler etc.

I think I gave you a good impression of the power of Fluent Security. There are lots of ways to make your own components for Fluent Security when the defaults don't work for you. It is open source available on Github, so you can easily participate and improve Fluent Security. The documentation is pretty good and Kristoffer Ahl really helped me out with some small issues I had when trying it out first time. Please share the article if you liked it and you really should try it out. Have fun!

You have disabled cookies. To leave me a comment please allow cookies at functionality level.

More Stories

Cover Image for Unblock downloaded files with PowerShell

Unblock downloaded files with PowerShell

MF

Marco Franssen /

Have you ever got in the situation that you downloaded a zip-file and figured out to late the files are blocked? So you extracted the zip file into a folder with existing items. It will be damn hard to figure out which files needs to be unblocked. Besides that it will cost you many work to do so by right-clicking all of the files and clicking the unblock button. Unblock file Luckily we have PowerShell and we can easily write a little script to execute the unblock operation on the files in a sp…

Cover Image for Install Windows 8 from rusty 256 MB USB stick

Install Windows 8 from rusty 256 MB USB stick

MF

Marco Franssen /

This is the fourth time I installed Windows 8. This time I installed it on my personal notebook instead of a VHD, because Windows 8 is finally ready to market. So I started with downloading the enterprise edition from my MSDN subscription. Unfortunately my USB drive died so I had no storage large enough to put the image on and boot from. So I started thinking to install it over the network. Luckily me I still had my rusty 10 year old 256MB USB drive which perfectly fits a Windows PE image. So I…

Cover Image for Delegate your equality comparisons

Delegate your equality comparisons

MF

Marco Franssen /

When using Linq on your Entity Framework objects, you often need to distinct your query results. Therefore you need to implement an IEqualityComparer for the more advance scenario's. For example if you want to distinct on a specific property, or maybe on multiple properties. However this forces you to write lots of infrastructure code to distinct each type. You probably would end up with several equality compare classes like this. However there is a solution which will save you the work to wri…

Cover Image for Install Windows 8 Consumer preview on vhd

Install Windows 8 Consumer preview on vhd

MF

Marco Franssen /

In a previous blog post I explained to you how to install Windows 8 developer preview on vhd, so you can boot from your vhd. Since there have changed a few small things I just add an updated manual below. The installation will take about 30 minutes. Step 0 Make sure you have at least 40Gb of free disk space for your vhd. Make sure you're running Windows 7. Step 1 Download the Windows 8 consumer preview. Download the Windows 7 USB/DVD tool to make yourself a bootable usb stick. Use the tool…