Feature-based authorization
It seems to me that if you rely on roles to authorize a web application it makes it very difficult to render UI or code based on a set of features. This means that each role would have be checked against for each feature.
if(User.IsInRole("SomeRole") || User.IsInRole("OtherRole")) { // Do something } // C#
@if(User.IsInRole("SomeRole") || User.IsInRole("OtherRole")) { // Do something } // Razor
This seems very rigid. It is for this reason that I started to think of the application in terms of features. Roles can have features and Members can have features. Next the features have to be authorized based on the Member's current feature-set.
if(SomeFeature) { // Do Something } // In C#
@if(SomeFeature) { <h1>Show Something</h1> } // In Razor
Members have roles
ASP.NET/MVC provides a framework to manage roles using SQL server (aspnet_regsql). We are using this out of the box and things are working fine so far.
Roles have features
In order to have more granular authorization we are attaching features to roles in a many to many reference table for features and aspnet_roles and Feature to aspnet_users tables in the same fashion.
Wrap everything in a feature when authorization is needed
Everything that needs to authorized is wrapped in a feature. Although, my heart would like to believe in a more concise method I can't fathom this would be possible.
Wrap on the server
Conditional statement for code that is only available to this feature:
if(Feature.Authorized(Features.SomeFeature))
{
// Code in here that processes the feature ...
}
Wrap on the client
Same as above but markup is wrapped up for the Feature
:
@if(Feature.Authorized(Features.SomeFeature))
{
<h1>Markup if feature exists.</h1>
});
Class to check authorization
The account service is called to authorize the feature for the previous conditional statements.
public static class Feature
{
public static Boolean Authorized(Features features)
{
var service = NinjectWebCommon.Kernel.Get<IAccountService>();
return service.IsAuthorized(Enum.GetName(typeof (Features), features));
}
}
enum
with Feature
s
This is a set of features that can be updated at design time to add a feature.
public enum Features
{
SomeFeature
}
Classes to create and find if Feature
s exist account service (con't)
The provides a set of methods to interact with features in the database.
IsAuthorized
method
Checks to see if this user has the given features. This helps with rendering or executing conditional code or UI. The bonus here is that I add feature to the database if it doesn't already exist. This means a developer can go about his/her work without any configuration aside from adding a Feature
s enum entry and then authorizing against it.
public override Boolean IsAuthorized(String name)
{
// Find the feature in the database.
// Add the feature to the database if not found.
var feature =
db.Features.SingleOrDefault(f => f.Name == name) ??
db.Features.Add(new Feature()
{
Name = name,
PageUrl = Request.Url.AbsolutePath
});
db.SaveChanges();
// MyFeatures holds a list of features available to the current user
// The feature set is queried as requested.
// The features are a union of features assigned to the user as well as
// features assigned to Roles that the current user.
return MyFeatures.Contains(feature.Name);
}
Current features available
Gets or creates a new list of features based on features and roles in the database:
public List<String> MyFeatures
{
get
{
var session = HttpContext.Current.Session;
// Get cached features or from stored procedure call.
// The stored procedure is of the UNION of features stated earlier.
var result = session[Constants.Features.Key] as List<String> ??
(session[Constants.Features.Key] = GetCurrentFeatures());
return features;
}
}
Gets Feature
s from database
private List<String> GetCurrentFeatures()
{
var user = Membership.GetUser(HttpContext.Current.User.Identity.Name);
var features = from f in Methods.GetCurrentFeatures((Guid)user.ProviderUserKey)
select f.Name;
return features.ToList();
}
This is my design but I was wondering if there are any flaws with this or best practices that I haven't thought of.