I have an existing database with a users table, and we are planning to take the database and use it for a new system built in ASP.NET MVC. However, what I am uncertain about
Whenever anyone tells you that something security-related is "easy," they are nearly always wrong. There are a lot of subtleties in security which non-experts tend to miss.
In particular, any form of authentication which does not explicitly deal with caching is inherently broken. When an action result is cached, this happens within ASP.NET, not necessarily within the ASP.NET MVC stack. If you examine the source code for AuthorizeAttribute, you will see that it contains some slightly tricky but effective code to ensure that it always runs, even when the action result is cached.
The best way, by far, to customize ASP.NET MVC authentication is to write a custom ASP.NET membership provider. I won't claim that this is foolproof, but there are fewer ways to get in trouble with a broken security implementation in this route then with other methods. A substantial advantage of this technique is that you can substitute a different authorization system at almost any time, with no code changes.
If you must implement a custom MVC attribute, then you should subtype AuthorizeAttribute and override AuthorizeCore, taking careful note of the comments in the source code regarding thread safety.
I had this exact same requirement. I had my own user and role schema and did not want to migrate to the asp.net membership schema but I did want to use the ASP.NET MVC action filters for checking authorization and roles. I had to do a fair amount of digging to find out exactly what needed to be done, but in the end it was relatively easy. I'll save you the trouble and tell you what I did.
1) I created a class that derived from System.Web.Security.MembershipProvider. MembershipProvider has a ton of abstract methods for all sorts of authentication-related functions like forgot password, change password, create new user, etc. All I wanted was the ability to authenticate against my own schema. So my class contained mainly empty overrides. I just overrode ValidateUser:
public override bool ValidateUser(string username, string password)
{
if (string.IsNullOrWhiteSpace(username) ||
string.IsNullOrWhiteSpace(password))
return false;
string hash = EncryptPassword(password);
User user = _repository.GetByUserName(username);
if (user == null) return false;
return user.Password == hash;
}
2) I created a class that derived from System.Web.Security.RoleProvider. Again, I just had empty implementations for all the fluff I did not need like creating and changing roles. I just overrode two methods:
public override string[] GetRolesForUser(string username)
{
User user = _repository.GetByUserName(username);
string[] roles = new string[user.Role.Rights.Count + 1];
roles[0] = user.Role.Description;
int idx = 0;
foreach (Right right in user.Role.Rights)
roles[++idx] = right.Description;
return roles;
}
public override bool IsUserInRole(string username, string roleName)
{
User user = _repository.GetByUserName(username);
if(user!=null)
return user.IsInRole(roleName);
else
return false;
}
3) Then I plugged these two classes into my web.config:
<membership defaultProvider="FirstlookMemberProvider" userIsOnlineTimeWindow="15">
<providers>
<clear/>
<add name="FirstlookMemberProvider" type="FirstlookAdmin.DomainEntities.FirstlookMemberProvider, FirstlookAdmin" />
</providers>
</membership>
<roleManager defaultProvider="FirstlookRoleProvider" enabled="true" cacheRolesInCookie="true">
<providers>
<clear/>
<add name="FirstlookRoleProvider" type="FirstlookAdmin.DomainEntities.FirstlookRoleProvider, FirstlookAdmin" />
</providers>
</roleManager>
That's it. The default authorization action filters will use these classes. You will still have to handle the login page sign in and sign off. Just use the standard forms authentication classes for this like you normally would.
Of course you can. I did it for my projects completely ignoring the membership provider.
You need to implement your own ActionFilter. Basically, it will intercepts control before a controller action is hit. Inside of it you decide whether to continue on to the action or redirect the user to login page.
For the attribute you can define any parameters you need to support your authentication/authorization model.
public class AuthorizationAttribute : ActionFilterAttribute, IActionFilter
{
public MyRole UserRole { get; set; }
void IActionFilter.OnActionExecuting (ActionExecutedContext filterContext)
{
// Decide whether to grant access to the action or redirect away
}
}
[Authorization (UserRole = MyRole.All)]
public class UserController : Controller
{
[Authorization (UserRole = MyRole.Admin)]
public ActionResult Delete ()
{
}
}
Regarding the concerns expressed in the comments. Yes, enabling output cache will interfere with authorization. One just has to be aware of that.
Explanation of the problem: ASP.NET MVC Tip #40 - Don’t Cache Pages that Require Authorization
You have at least two possibilities
IHttpModule
that will fill all the necessary data for the logged in user (including roles) and you can use existing action filtersThe second choice can be used with regular web forms as well.