Restival Part 6: Who Am I, Revisited
Posted by Dylan Beattie on 08 December 2015 • permalinkNote: code for this instalment is in https://github.com/dylanbeattie/Restival/tree/v0.0.6
In the last instalment, we looked at adding HTTP Basic authentication to a simple HTTP endpoint - GET /whoami - that returns information about the authenticated user.
Well... I didn't like it. Both the OpenRasta and the WebAPI implementations felt really over-engineered, so I kept digging and discovered a few things that made everything much, much cleaner.
Basic auth in OpenRasta - Take 2
There's an HTTP Basic authentication feature baked into OpenRasta 2.5.x, but all of the classes are marked as deprecated so in my first implementation I avoided using it. After talking with Seb, the creator of OpenRasta, I understand a lot more about the rationale behind deprecating these classes - they're earmarked for migration into a standalone module, not for outright deletion, and they'll definitely remain part of the OpenRasta 2.x codebase for the foreseeable future.
Armed with that knowledge, and the magical compiler directive #pragma warning disable 618 that stops Visual Studio complaining about you using deprecated code, I switched Restival back to running on the OpenRasta NuGet package instead of my forked build, and reimplemented the authentication feature - and yes, it's much, much nicer.
There's a RestivalAuthenticator which implements OpenRasta's IBasicAuthenticator interface - as with the other frameworks, this ends up being a really simple wrapper around the IDataStore:
public class RestivalAuthenticator : IBasicAuthenticator {
private readonly IDataStore db;public RestivalAuthenticator(IDataStore db) {
this.db = db;
}public AuthenticationResult Authenticate(BasicAuthRequestHeader header) {
var user = db.FindUserByUsername(header.Username);
if (user != null && user.Password == header.Password) return (new AuthenticationResult.Success(user.Username, new string[] { }));
return (new AuthenticationResult.Failed());
}public string Realm { get { return ("Restival.OpenRasta"); } }
}
and then there's the configuration code to initialise the authentication provider.
ResourceSpace.Uses.PipelineContributor<AuthenticationContributor>();
ResourceSpace.Uses.PipelineContributor<AuthenticationChallengerContributor>();
ResourceSpace.Uses.CustomDependency<IAuthenticationScheme, BasicAuthenticationScheme>(DependencyLifetime.Singleton);
ResourceSpace.Uses.CustomDependency<IBasicAuthenticator, RestivalAuthenticator>(DependencyLifetime.Transient);
This one stumped me for a while, until I realised that - unlike, say, Nancy, which just does everything by magic, you need to explicitly register both the AuthenticationContributor and the AuthenticationChallengerContributor. These are the OpenRasta components that handle the HTTP header parsing, decoding and the WWW-Authenticate challenge response, but if you don't explicitly wire them into your pipeline, your custom auth classes will never get called.
Basic auth in WebAPI - Take 2
As part of the last instalment, I wired up the LightInject IoC container to my WebAPI implementation. I love LightInject, but something I had never previously realised is that LightInject can do property injection on your custom attributes. This is a game-changer, because previously I'd been following a pattern of using purely decorative attributes - i.e. with no behaviour - and then implementing a separate ActionFilter that would check for the presence of the corresponding attribute before running some custom code - and all this just so I could inject dependencies into my filter instances.
Well, with LightInject up and running, you don't need to do any of that - you can just put a public IService { get; set; } onto your MyCustomAttribute class, and LightInject will resolve IService at runtime and inject an instance of MyAwesomeService : IService into your attribute code. Which means the half-a-dozen classes worth of custom filters and responses from the last implementation can be ripped out in favour of a single RequireHttpBasicAuthorizationAttribute - which overrides WebAPI's built-in AuthorizeAttribute class to provide authorization header parsing, WWW-Authenticate challenge response, and hook the authentication up to our IDataStore interface.
I'm much happier now with all four implementations, so it raises the interesting question of how much development time is really worth. Based on the code provided here, I suspect a good developer could implement HTTP Basic auth on any of these frameworks in about fifteen minutes - but something that takes fifteen minutes to implement doesn't really count if it takes you two days to work out how to do that fifteen-minute implementation.
In forthcoming instalments, we're going to be adding hypermedia, HAL+JSON and resource expansion - as we move further away from basic HTTP capabilities and into more advanced REST/serialization/content negotiation, it'll be interesting to see how our four frameworks measure up.