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.