My name is Rex Morgan and I'm a Staff Software Engineer in Austin, TX working at Indeed.

I love learning new languages and tinkering with new technologies. I'm currently having fun with Elixir, Java, C#, and Kubernetes.

FubuMVC: Authentication

If you're using FubuMVC and your site requires users to login, you'll probably want to use the built in authentication facilities that FubuMVC provides. In this post, I'll attempt to explain how this works. I'm going to write an authentication convention to block access to certain actions from unauthenticated users.

Overview

  1. Come up with a convention to determine which action calls require the user to be authenticated
  2. Write a behavior to redirect a user if they're not authenticated
  3. Teach FubuMVC about the convention
  4. Tell FubuMVC to use the convention
  5. Let FubuMVC know when someone has logged in or out

Step 1: Come up with a convention

For this example, my convention is going to be any action call marked with a [Secured] attribute, will require an authenticated user in order to execute. As you'll see, this can be adapted to match whatever convention you want to use.

To start, I'll just create a simple marker attribute and throw it on the action calls that I want to be secured.

public class SecuredAttribute : Attribute
{
}

public class DashboardEndpoint
{
    [Secured]
    public DashboardRequestModel Get(DashboardRequestModel input)
    {
        return new DashboardRequestModel();
    }
}

Since we've marked the action calls that we want to secure, we'll be able to pick them out later when we want to teach FubuMVC about our convention.

Step 2: Write a behavior

In order to do the actual work of checking to see if the user is logged in, and redirecting them if they're not, I'll need to create a behavior. If you're unfamiliar with behaviors, I'd highly recommend reading these articles about them.

The behavior will depend on the ISecurityContext provided by FubuMVC, so I can check if the user has been authenticated. If the user has not been authenticated, I'll just redirect them to the login page.

It's important to note that, by default, the ISecurityContext is a wrapper around the current HttpContext. This means that FubuMVC is really just using standard ASP.NET forms authentication.

When FubuMVC calls the behavior, it will look at the result coming from the performInvoke() method. If the method returns DoNext.Continue, then the next behavior in the chain will be called. However, if it returns DoNext.Stop, then it will not call the next behavior in the chain and execution of the request stops. It actually doesn't just stop, if you've been through any behaviors that wrap the authentication behavior, you will begin to start calling the afterInsideBehavior method on these.

public class AuthenticationRequiredBehavior : BasicBehavior
{
    private readonly ISecurityContext _securityContext;
    private readonly IUrlRegistry _urlRegistry;
    private readonly IOutputWriter _outputWriter;

    public AuthenticationRequiredBehavior(ISecurityContext securityContext, IUrlRegistry urlRegistry, IOutputWriter outputWriter)
        : base(PartialBehavior.Ignored)
    {
        _securityContext = securityContext;
        _urlRegistry = urlRegistry;
        _outputWriter = outputWriter;
    }

    protected override DoNext performInvoke()
    {
        if(_securityContext.IsAuthenticated())
        {
            return DoNext.Continue;
        }

        var url = _urlRegistry.UrlFor(new LoginRequestModel());
        _outputWriter.RedirectToUrl(url);
        return DoNext.Stop;
    }
}

Step 3: Teach FubuMVC about the convention

So I've setup our action calls to use the convention and the behavior to kick unauthorized people out. Now I just need to teach FubuMVC what that convention is, and how it should be implemented. In order to do that, I'll need to create an implementation of the IConfigurationAction interface (these are typically called conventions.) My convention will modify the graph and wrap all action calls that have the Secured attribute with our behavior.

In the convention, I have access to all of the Actions that FubuMVC knows about. I'm able to filter those actions to only get those which implement the SecuredAttribute, and then wrap those with the behavior I just created.

public class AuthenticationConvention : IConfigurationAction
{
    public void Configure(BehaviorGraph graph)
    {
        graph
            .Actions()
            .Where(c => c.HasAttribute<SecuredAttribute>())
            .Each(c => c.WrapWith<AuthenticationRequiredBehavior>());
    }
}

This is where I could change it up and use another convention if I wanted to. Instead of filtering the actions based on attributes, I can check for anything. If I wanted to filter for all actions with an input type that starts with "Add" or "Edit", that's possible by using this convention.

public class AuthenticationConvention : IConfigurationAction
{
    public void Configure(BehaviorGraph graph)
    {
        graph
            .Actions()
            .Where(c => c.HasInput && c.InputType().Name.StartsWith("Add") || c.InputType().Name.StartsWith("Edit"))
            .Each(c => c.WrapWith<AuthenticationRequiredBehavior>());
    }
}

Step 4: Tell FubuMVC to use the convention

In order to tell FubuMVC to apply the convention, I need to call this method from the FubuRegistry.

public class MyRegistry : FubuRegistry
{
    public MyRegistry()
    {
        ApplyConvention<AuthenticationConvention>();
    }
}

Now, if I run my project and attempt to browse to an action that has been locked down, I get redirected to the login page, which is exactly what I wanted it to do.

Step 5: Let FubuMVC know when someone has logged in or out

In order to tell FubuMVC that a user has been logged in, I need to depend on IAuthenticationContext in the action that logs users in. Once I've verified the user's credentials are correct, I can call the ThisUserHasBeenAuthenticated method on IAuthenticationContext to let FubuMVC know.

When I want to log a user out, call SignOut on the IAuthenticationContext interface.

Gotcha!

As I stated earlier, the default authentication in FubuMVC is really just a wrapper for forms authentication in ASP.NET. So, in order to use the ISecurityContext, you'll need to turn on forms authentication in your web.config, otherwise the ISecurityContext will always say that the user is authenticated.

<system.web>
    <authentication mode="Forms">
        <forms loginUrl="~/login" timeout="25" slidingExpiration="true" />
    </authentication>
</system.web>