I don't like the built in membership providers. I've decided to roll my own. I'm trying to come up with a good method for performing authorization at the action level. Here are the requirements that I'm trying to go by:
- Attribute usage - I like this since it controls at a very high level in the call stack and is a nice place to organize permissions.
- No magic strings - This is a reason why I'm straying away from the current role providers. I don't want to leave strings lying around that can't be easily renamed.
- Permissions should can be composed of one other permission. Example:
ReadWrite
has permission forRead
. Just like or'ing with an enum.
NOTE: Some think this set of requirements is too broad (see comments). I don't think so, I think they're fairly straightforward.
The biggest showstopper is attribute usage. There can only be "constant expressions, typeof expressions or array creation expression of an attribute parameter type".
I was thinking of perhaps having something like this to make operations have static access. Inside of the attribute, it would "convert" the int to the actual Permission or something...:
public static class Operations
{
public static class SectionA
{
public const int Read = 1;
public const int ReadWrite = 2;
}
public static class SectionB
{
// ... and so on...
}
}
But it really limits composition. I'm sure you're thinking "why don't you go the enum route?" well I want to plan for things to change and don't want to limit to 32 (int) or 64 (long) operations and have to do a massive rewrite later (also in the db that's just ugly).
Also, if there is a better alternative than attributes on actions/controllers, then I'm all ears for suggestions.
EDIT: Also from this post, I've read about the BitArray
class. It seems kind of ugly, especially with the arbitrary storage in the database.
So @thomas seems to have a nice answer, buts its more wrapping your requirement of using enums, taking that into Roles that
IPricipal
will understand. My solution is from bottom up, so you can use thomas' solution on top of mine to implementIPrincipal
I really needed something similar to what you want and was always scared with Forms Authentication, (yes you're scared too and I know it, but hear me out) So I always rolled out my own cheap authentication with forms, but a lot of things changed while I was learning mvc (in the last couple weeks) Forms auth is very dis separate and its very flexible. Essentially you're not really using forms auth, but your just plugging your own logic into the system.
So here is how I tackled this, (beware I am a learner myself).
Summary:
IIdentity
.GenericPrincipal
with a list of roles in strings (I know, no magic strings...keep reading)Once you do the above, MVC understands enough to give you what you want! You can now use
[Authorize(Roles = "Write,Read")]
over any controller and MVC will do almost everything. Now for no magic strings, all you have to do it create a wrapper around that attribute.Long answer
You use the Internet Application Template that comes with MVC, So first you begin by creating MVC project, in the new dialog, say you want an Internet Application.
When you check the application, it will have one main class that overrides forms authentication.
IMembershipService
Remove the local MembershipProvider variable __provider_ and in this class you should atleast add logic into theValidateUser
Method. (Try adding a fake authentication to one user/pass) Also see the default v test application created in VS.Implement
IIdentity
Remember we're still using the Default Internet Application template that comes with an MVC project.
So now AccountController.LogOn() should look like this:
So what you're doing is setting a forms ticket like a session and then we'll read from it on every request like this: Put this in Global.asax.cs
I asked a question one how efficient and correct the above method was here.
Ok now you are almost done, you can decorate your controllers like this:
[Authorize(Roles="RoleA,RoleB")]
(more on the strings later)Theres one small problem here, if you decorate your controller with
AuthorizeAttribute
, and the logged user does not have a particular permission, instead of saying "access denied" by default the user will be re directed to the login page to login again. You fix this like this(I tweaked this from an SO answer):Now all you do is add another wrapper around the
AuthorizeAttribute
to support strong types that will translate into the strings that Principal expects. See this article for more.I plan to update my application to use strong types later, I'll update this answer then.
I hope it helped.
Seems like you want something very flexible and dependless of what can be demanded for security check. So, it depends on "how far are you ready to go".
To help make this way be a right direction I strongly recommend you to look to the side of Claims-based Access Control. And take this article as a starting point and ASP.NET MVC example.
But remember that it is a complex topic. Very flexible (even allowing Federated Access Control without any code changes) but complex.
We had to go this way to make our apps completely unavailable of those "right checking" implementations. All our systems know is what "claim" they need to perform certain action and asks for it based on provided user identity (which is a "claim" too). Roles, permissions and other claims can be easily "translated" to those App-specific "claims" that make sense for our Apps. Full flexibility.
P.S. It doesn't solve the technical problems of "magic strings" and alike (you have to think that depend on your situation) but gives you very flexible access control architecture.
First of all, I have to thank you for sucking me into answering this ;)
This is a long answer, and is only a starting point. You have to figure out how to assign roles to users and how to recreate them in the
AuthenticateRequest
.If this does not answer your question, I hope it will be an inspiration. Enjoy!
Decorate the controller actions
I started to decorate the two actions in the default
HomeController
:All users in the ReadWrite role should then be granted access. I opted here to use an enum as a type safe placeholder for the magic strings. The role of this enum is nothing else than being a placeholder. There are no composite enum values, that has to be maintained somewhere else. More on that later.
Implement a new authorization attribute
Since the strings are gone, I need a new authorize attribute:
The
RoleSet
wraps a set of enum values and verifies if anIPrincipal
is a member of one of them:Maintain roles
The
CompositeRoleSet
is where composite roles are registered and handled.CreateDefault()
is where all composites are registered.Resolve()
will take a list of roles (enum values) and convert the composites to their single counterparts.Wiring it up
We need an authenticated user to work on. I cheated and hard-coded one in global.asax:
Finally, we need an
IPrincipal
which understand all this: