views:

1610

answers:

5

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 is whether or not I am able to create a login system that doesn't use the built in account controller or a regular membership provider so that we can still use the existing table structure.

So my question is, would this be possible? Or even particularly difficult to do if it is?

What is the most widely accepted way of doing things and the simplest?

+3  A: 

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

Developer Art
Can you create custom ones then? Of course businesses vary, so you may want `Role.IsAccountsAdmin` instead of just `Role.Admin`.
Kezzer
These are the custom ones, just the names coincide. Will update them in a moment to remove ambiguity.
Developer Art
Would this simply go in the pre-generated AccountController file or would I need to create my own?
Liam
This code is completely broken. Turn on caching and your "authentication" may vanish. The problem is that you didn't inherit from AuthorizeAttribute, which cooperates with caching but still allows customization. You can work around this with Order, but that's fragile. AuthorizeAttribute does it right. See also: http://blogs.teamb.com/craigstuntz/2009/09/09/38390/
Craig Stuntz
That tip is obsolete. It was based on a pre-release version of MVC and the bug was fixed in a later version. Caching works fine for authorized actions in the shipping version of MVC if you don't break it in a reimplementation of AuthorizeAction.
Craig Stuntz
So does that mean the code in this comment would work without "breaking"?
Liam
Liam, the code in this answer is broken, like I said. Matt Wrock is doing it correctly, though.
Craig Stuntz
A: 

Agree it is possible and quite easy. You can event implement your own action filters to recognize user roles.

Asp.net MVC is very flexible and open, so you can implement you own parts on any level.

twk
A: 

You have at least two possibilities

  • a custom action filter attribute that will provide your authorization check
  • a custom IHttpModule that will fill all the necessary data for the logged in user (including roles) and you can use existing action filters

The second choice can be used with regular web forms as well.

Robert Koritnik
+4  A: 

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.

Craig Stuntz
At present, I don't have a "set" way of doing things, I do know that I have to use the existing data and table structure, so the easiest, simplest and most secure way of doing it is ideally what I'm after.Is it particularly easy to write a custom membership provider? I've not done it before and at first glance it seems a little more difficult to do than it actually is?
Liam
Here is a video http://www.asp.net/learn/videos/video-189.aspx and an article http://www.devx.com/asp/Article/29256 on how to do it. It's work, but not hard work. If this seems like too much work to do, well, like I said, other solutions may only seem "easier" because they are essentially broken, with huge gaps in security, features, and modularity.
Craig Stuntz
I'd much rather use a way that isn't broken personally, especially as this app will be externally accessible as well.I'll have a go and see what happens.
Liam
+13  A: 

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 derrived from System.Web.Security.MembershipProvider. MembershipProvider has a ton or 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 I my class contained mainly empty overrides. I just overrided ValidateUser:

    public override bool ValidateUser(string username, string password) { if(string.IsNullOrEmpty(password.Trim())) return false;

    string hash = EncryptPassword(password);
    User user = _repository.GetByUserName(username);
    if (user == null) return false;
    
    
    if (user.Password == hash)
    {
        return true;
    }
    
    
    return false;
    

    }

  2. I created a class that derrived from System.Web.Security.RoleProvider. Again, I just had empty implementations for al the fluff I did not need like creating and changing roles. I just overrided 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;
       }
  1. Then I plug 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>

Thats 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.

Matt Wrock
+1. Customizing the provider is a correct way to do it. Thanks for pointing out that it's also not necessarily a lot of work.
Craig Stuntz
Oh, worth pointing out that the password should really be salted with a nonce before hashing, though.
Craig Stuntz
I am sorry to revive this and bother you, but is it possible to get some more information on your solution? I am a little confused on how you did the role management.
Stacey
I blogged on this and provided more complete code samples. I hope this post will be helpful: http://mattwrock.com/post/2009/10/14/Implementing-custom-Membership-Provider-and-Role-Provider-for-Authinticating-ASPNET-MVC-Applications.aspx
Matt Wrock