This project has moved and is read-only. For the latest updates, please go here.

Support "Multi-tenancy" In AuthenticationOptions, etc

Aug 20, 2014 at 1:56 PM
Edited Aug 20, 2014 at 2:29 PM
Hello Katana team,

Problem

I've been unable to cleanly extend the "built-in" authentication middleware because of its dependency on a concrete instance of AuthenticationOptions (and subclasses per implementation).

Presently the auth middleware is initialized at start-up and any configuration (API KEY, API SECRET, etc) cannot be changed on a per-request or per-tenant basis. I've seen some suggestion of multiple middleware instances per tenant as a solution but this is not very efficient or (feasible) when you have N tenants.

In my case each of my customers has their own OAUTH api credentials for each service and I need to be able to set them per-request based on the tenant scope. My problem, although seemingly common, is not the only reason to consider my proposed change.

The current dependency on a concrete implementation of [Middleware]AuthenticationOptions and AuthenticationOptions limits extensibility to the point that many are forced to "roll their own" copies of existing middleware with minor changes for their individual needs.

Proposed Design Change

I propose a design change, of which I'd like to contribute code, to address this limitation and allow for an optional alternative way to load middleware AuthenticationOptions. To achieve this we would depend on an interface IAuthenticationOptions and I[MiddlewareName]AuthenticationOptions for each specific middleware implementation.

These interfaces would have default implementations for each middleware. For example, for Facebook there would be a DefaultFacebookAuthenticationOptions which would implement the interface and use the current (static) approach to loading AppId and AppSecret.

A second scenario-specific implementation (which would be left to someone else to implement) could be MultitenantFacebookAuthenticationOptions which also implements the interface and identifies the tenant based on the request and then uses a datastore to load the AppId and AppSecret.

It would probably make most sense to have a FacebookAuthenticationOptionsBase abstract class which contains most of the needed functionality. We would also need to modify the existing FacebookAuthenticationOptions class to support the new interface but without breaking backward compatibility.

I have already proven this concept in my own project. In order to implement this we would need to change several authentication middleware classes which have dependencies on AuthenticationOptions as well as existing middleware which has dependencies on concretes of their respective implementations (e.g. FacebookAuthenticationOptions). Likewise, Extension Methods related to adding middleware to the pipeline would be modified to depend on the aforementioned interfaces instead of concretes.

Backwards Compatibility

This design change would have no impact on existing implementations. Developers would still be able to use app.UseFacebookAuthentication (et al) as well as the existing FacebookAuthenticationOptions class.

A new option for developers will be to provide/inject an IAuthenticationOptions when adding the middleware to the pipeline. Typically that would be done as follows:
// Old approach still supported
app.UseFacebookAuthentication(appId, appSecret);

// A new approach to inject an IFacebookAuthenticationOptions implementation
app.UseFacebookAuthentication(new MultitenantFacebookAuthenticationOptions());
Both approaches would be supported under the hood as such:
public static class FacebookAuthenticationExtensions
{
    public static IAppBuilder UseFacebookAuthentication(this IAppBuilder app, IFacebookAuthenticationOptions options)
    {
        if(app == null) throw new ArgumentNullException("app");
        if(options == null) throw new ArgumentNullException("options");

        app.Use(typeof (FacebookAuthenticationMiddleware), app, options);

        return app;
    }

    public static IAppBuilder UseFacebookAuthentication(this IAppBuilder app, string appId, string appSecret)
    {
        return UseFacebookAuthentication(
            app,
            new DefaultFacebookAuthenticationOptions
            {
                AppId = appId,
                AppSecret = appSecret,
            });
    }
}
Existing custom middleware, such as those provided by the owin-middleware/OwinOAuthProviders project should continue to work but will need to update their Microsoft.Owin.Security dependencies if they want to stay current. I believe this is the only burden added by my proposed design.

Finally...

I'm eager to see this implemented as I believe it is a design improvement which makes authentication middleware much more extensible without making it more complex. I wanted to get buy-in before I started the code changes required to implement the change.

So, Katana team, what do you think? :)
Aug 20, 2014 at 3:47 PM