58

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?

4 Answers 4

80

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.

Sign up to request clarification or add additional context in comments.

4 Comments

+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.
Oh, worth pointing out that the password should really be salted with a nonce before hashing, though.
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.
I blogged on this and provided more complete code samples. I hope this post will be helpful: mattwrock.com/post/2009/10/14/…
11

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.

5 Comments

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?
Here is a video asp.net/learn/videos/video-189.aspx and an article 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.
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.
Where is the source for AuthorizeAttribute?
@Eduardo: You get it with the MVC source code
10

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

7 Comments

Can you create custom ones then? Of course businesses vary, so you may want Role.IsAccountsAdmin instead of just Role.Admin.
These are the custom ones, just the names coincide. Will update them in a moment to remove ambiguity.
Would this simply go in the pre-generated AccountController file or would I need to create my own?
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: blogs.teamb.com/craigstuntz/2009/09/09/38390
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.
|
1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.