How I authenticate with fubuMVC

I’ve been changing my application to use fubuMVC and have been writing about my experiences with this process - this post is the first one that describes how I authenticate a user.
It once again illustrates the simple, yet very extensible nature of the framework.

Before I go into detail with some test driven specifications, there are a few things to point out in preparation:

Firstly, I’m using forms authentication:

    <authentication mode="Forms">
      <forms protection="Encryption" loginUrl="~/login" timeout="25" slidingExpiration="true" />
    </authentication>

Secondly, to assist with authentication the fubuMVC framework provides a security abstraction that allows you to identify the current forms authentication user, so I’m going to be using that.
Here’s the interface for the security context:

namespace FubuMVC.Core.Security
{
  public interface ISecurityContext
  {
    IIdentity CurrentIdentity { get; }

    IPrincipal CurrentUser { get; set; }

    bool IsAuthenticated();
  }
}

Last of all, in order for me to authenticate in the request pipeline, with fubuMVC I need to write a custom behaviour which I can insert into the behaviour chain executed on each relevant request.
It is going to need to use the IContinuationDirector from the fubuMVC framework and I recently illustrated how I ensure the registration of that abstraction.

So, here are my test driven specifications for this new behaviour (and note, these were refined and built up over a few iterations with TDD and are presented here as the final version):

    [Subject(typeof(AuthenticationBehaviour))]
    public class when_invoked_and_the_user_is_authenticated
    {
        Establish context = () =>
        {
            _identity = new Mock<IIdentity>();
            _currentUser = new Mock<IPrincipal>();
            _currentUser.Setup(u => u.Identity).Returns(_identity.Object);

            _securityContext = new Mock<ISecurityContext>();
            _securityContext.Setup(s => s.IsAuthenticated()).Returns(true);
            _securityContext.Setup(s => s.CurrentUser).Returns(_currentUser.Object);

            _continuationDirector = new Mock<IContinuationDirector>();
            _outputWriter = new Mock<IOutputWriter>();
            _authenticatedUserProcessor = new Mock<IProcessAuthenticatedUsers>();

            _nextBehaviour = new Mock<IActionBehavior>();

            _authenticationBehaviour = new AuthenticationBehaviour(_securityContext.Object, 
                                                            () => _continuationDirector.Object,
                                                            _outputWriter.Object,
                                                            _authenticatedUserProcessor.Object)
                                           {
                                               InsideBehavior = _nextBehaviour.Object
                                           };
        };

        Because of = () => _authenticationBehaviour.Invoke();

        It should_tell_the_user_processor_to_process_the_current_user = () =>
            _authenticatedUserProcessor.Verify(u => u.Process(_identity.Object));

        It should_continue_to_invoke_further_behaviours = () => 
            _nextBehaviour.Verify(b => b.Invoke());

        static Mock<ISecurityContext> _securityContext;
        static AuthenticationBehaviour _authenticationBehaviour;
        static Mock<IActionBehavior> _nextBehaviour;
        static Mock<IProcessAuthenticatedUsers> _authenticatedUserProcessor;
        static Mock<IPrincipal> _currentUser;
        static Mock<IContinuationDirector> _continuationDirector;
        static Mock<IOutputWriter> _outputWriter;
        static Mock<IIdentity> _identity;
    }

In my specification I’m setting up an identity and principal which are provided by the ISecurityContext CurrentUser.
I’m setting IsAuthenticated to true to emulate the user being authenticated and I verify that it should tell the user processor to process the user and it should continue to invoke further behaviours.

I also have the specification for when the user is not authenticated:

    [Subject(typeof(AuthenticationBehaviour))]
    public class when_invoked_and_the_user_is_not_authenticated
    {
        Establish context = () =>
        {
            _securityContext = new Mock<ISecurityContext>();
            _securityContext.Setup(s => s.IsAuthenticated()).Returns(false);

            _continuationDirector = new Mock<IContinuationDirector>();
            _outputWriter = new Mock<IOutputWriter>();
            _authenticatedUserProcessor = new Mock<IAuthenticatedUserProcessor>();

            _nextBehaviour = new Mock<IActionBehavior>();

            _authenticationBehaviour = new AuthenticationBehaviour(_securityContext.Object, 
                                                            () => _continuationDirector.Object,
                                                            _outputWriter.Object,
                                                            _authenticatedUserProcessor.Object)
            {
                InsideBehavior = _nextBehaviour.Object
            };
        };

        Because of = () => _authenticationBehaviour.Invoke();

        It should_ensure_the_response_is_not_authorised = () =>
            _outputWriter.Verify(o => o.WriteResponseCode(HttpStatusCode.Forbidden));

        It should_redirect_the_user_so_that_they_can_login = () =>
            _continuationDirector.Verify(c => c.RedirectToCall(Moq.It.Is<ActionCall>(a => 
                        a.HandlerType == typeof(LoginController) && a.Method.Name == "Root")));

        It should_not_continue_to_invoke_further_behaviours = () =>
            _nextBehaviour.VerifyNeverHappened(b => b.Invoke());

        static Mock<ISecurityContext> _securityContext;
        static AuthenticationBehaviour _authenticationBehaviour;
        static Mock<IActionBehavior> _nextBehaviour;
        static Mock<IAuthenticatedUserProcessor> _authenticatedUserProcessor;
        static Mock<IContinuationDirector> _continuationDirector;
        static Mock<IOutputWriter> _outputWriter;
    }

In this scenario I want to write a forbidden response code, redirect the user to the login page and not invoke any further behaviours.

Here’s the implementation of my behaviour:

    public class AuthenticationBehaviour : BasicBehavior
    {
        readonly ISecurityContext _securityContext;
        readonly IContinuationDirector _continuationDirector;
        readonly IOutputWriter _outputWriter;
        readonly IProcessAuthenticatedUsers _authenticatedUserProcessor;

        public AuthenticationBehaviour(ISecurityContext securityContext,
                                        Func<IContinuationDirector> continuationDirectorFactory,
                                        IOutputWriter outputWriter,
                                       IProcessAuthenticatedUsers authenticatedUserProcessor)
            : base(PartialBehavior.Ignored)
        {
            _securityContext = securityContext;
            _continuationDirector = continuationDirectorFactory();
            _outputWriter = outputWriter;
            _authenticatedUserProcessor = authenticatedUserProcessor;
        }

        protected override DoNext performInvoke()
        {
            if(!_securityContext.IsAuthenticated())
            {
                _outputWriter.WriteResponseCode(HttpStatusCode.Forbidden);
                _continuationDirector.RedirectToCall(
ActionCall.For<LoginController>(c => c.Root())); return DoNext.Stop; } _authenticatedUserProcessor.Process(_securityContext.CurrentUser.Identity); return DoNext.Continue; } }

To insert this new behaviour into the behaviour graph I need a convention:

    public class AuthenticationConvention : IConfigurationAction
    {
        public void Configure(BehaviorGraph graph)
        {
            graph.Actions()
                .Where(c => !c.HandlerType.Namespace.Contains("Home") 
                                && !c.HandlerType.Namespace.Contains("Login")
                                && !c.HandlerType.Namespace.Contains("Signup")
                .Each(c => c.WrapWith<AuthenticationBehaviour>());
        }
    }

And I now add this convention to my fubu registry:

public class XercesFubuRegistry : FubuRegistry
    {
        public XercesFubuRegistry()
        {
            IncludeDiagnostics(true);

            Applies.ToThisAssembly();

            Actions.IncludeClassesSuffixedWithController();

            Routes.HomeIs<HomeController>(c => c.FrontPage())
                    .IgnoreControllerNamespaceEntirely()
                    .UrlPolicy<EnsureControllersCanHaveARootUrlPolicy>();

            ApplyConvention<PermanentRedirectionConvention>();
            ApplyConvention<SimpleUnitOfWorkConvention>();
            ApplyConvention<AuthenticationConvention>();            <- new line

            this.UseSpark();

            Views.TryToAttachWithDefaultConventions();

            Output.ToJson.WhenCallMatches(action => action.Returns<AjaxResponse>());
        }
    }

In my next post I’ll show what happens when the authenticated user is processed by the IProcessAuthenticatedUsers implementation and how I make the current user available to my input models.



0 comments: