views:

197

answers:

2

I'm using ASP.NET MVC and I am trying to separate a lot of my logic. Eventually, this application will be pretty big. It's basically a SaaS app that I need to allow for different kinds of clients to access. I have a two part question; the first deals with my general design and the second deals with how to utilize in ASP.NET MVC

Primarily, there will initially be an ASP.NET MVC "client" front-end and there will be a set of web-services for third parties to interact with (perhaps mobile, etc).

I realize I could have the ASP.NET MVC app interact just through the Web Service but I think that is unnecessary overhead.

So, I am creating an API that will essentially be a DLL that the Web App and the Web Services will utilize. The API consists of the main set of business logic and Data Transfer Objects, etc. (So, this includes methods like CreateCustomer, EditProduct, etc for example)

Also, my permissions requirements are a little complicated. I can't really use a straight Roles system as I need to have some fine-grained permissions (but all permissions are positive rights). So, I don't think I can really use the ASP.NET Roles/Membership system or if I can it seems like I'd be doing more work than rolling my own. I've used Membership before and for this one I think I'd rather roll my own.

Both the Web App and Web Services will need to keep security as a concern. So, my design is kind of like this:

  • Each method in the API will need to verify the security of the caller

  • In the Web App, each "page" ("action" in MVC speak) will also check the user's permissions (So, don't present the user with the "Add Customer" button if the user does not have that right but also whenever the API receives AddCustomer(), check the security too)

  • I think the Web Service really needs the checking in the DLL because it may not always be used in some kind of pre-authenticated context (like using Session/Cookies in a Web App); also having the security checks in the API means I don't really HAVE TO check it in other places if I'm on a mobile (say iPhone) and don't want to do all kinds of checking on the client

However, in the Web App I think there will be some duplication of work since the Web App checks the user's security before presenting the user with options, which is ok, but I was thinking of a way to avoid this duplication by allowing the Web App to tell the API not check the security; while the Web Service would always want security to be verified

Is this a good method? If not, what's better? If so, what's a good way of implementing this. I was thinking of doing this:

In the API, I would have two functions for each action:

// Here, "Credential" objects are just something I made up
public void AddCustomer(string customerName, Credential credential
                           , bool checkSecurity)
{
  if(checkSecurity)
  {
    if(Has_Rights_To_Add_Customer(credential)) // made up for clarity
    {
      AddCustomer(customerName);
    }
    else
      // throw an exception or somehow present an error
  }
  else
    AddCustomer(customerName);
}

public void AddCustomer(string customerName)
{
  // actual logic to add the customer into the DB or whatever



  //  Would it be good for this method to verify that the caller is the Web App
  //  through some method? 
}

So, is this a good design or should I do something differently?


My next question is that clearly it doesn't seem like I can really use [Authorize ...] for determining if a user has the permissions to do something. In fact, one action might depend on a variety of permissions and the View might hide or show certain options depending on the permission. What's the best way to do this? Should I have some kind of PermissionSet object that the user carries around throughout the Web App in Session or whatever and the MVC Action method would check if that user can use that Action and then the View will have some ViewData or whatever where it checks the various permissions to do Hide/Show?

+1  A: 

What you propose will not work. Actions can be cached, and when they are, the action (and hence your home-rolled security) does not run. ASP.NET membership, however, still works, since the MVC caching is aware of it.

You need to work with ASP.NET membership instead of trying to reinvent it. You can, among other things:

  • Implement a custom membership provider or role provider.
  • Subtype AuthorizeAttribute and reimplement AuthorizeCore.
  • Use Microsoft Geneva/Windows Identity Foundation for claims-based access.

Also, I completely disagree with ChaosPandion, who suggests making structural changes in your code before profiling. Avoiding exceptions for "performance" reasons is absurd -- especially the idea that the mere potential to throw an exception for invalid users will somehow tank the performance for valid users. The slowest part of your code is likely elsewhere. Use a profiler to find the real performance issues instead of jumping on the latest micro-"optimization" fad.

The correct reason to avoid exceptions for authorizations is that the correct way to indicate an attempt at unauthorized access in a web app is to change the HTTP status code to 401 Unauthorized, not throwing an exception (which would return 500).

Craig Stuntz
Can this caching be disabled? If what your saying is true then it looks like you cannot do any Hide/Show based on permissions in your Action/View because it will just be cached and therefore not run either. So all you can do in ASP.NET MVC is simple "You can run this action method or you cannot?"Also, this caching seems problematic for other parts of my app (say reporting) if it is always just going to present stale data and not run the code. Any advice on that? Thanks for your answer.
JustAProgrammer
Yes, you can choose not to cache. But in this case (trying to facilitate a fundamentally broken approach to authentication) that's like curing acne with decapitation (yes, it works, but...). No, caching does *not* mean that you cannot have permissions on an action, just that the way you propose to do it does not work. If you work within the existing authentication instead of trying to re-implement it, then caching and authentication work hand in hand. Yes, the reason not to cache is when stale data is unacceptable. That is application-specific, which is why caching is off by default.
Craig Stuntz
Also, you can set caching to work per-client, not on the server. That way, the user will get the same privileges if he refreshes (no security run), however, if another user tries to access the page from another computer the server will serve him a new page (and thereby running the security again). Still I agree with Craig that you should probably try to make it work with the Membership and Role-system.
Alxandr
A: 

Define your authorisation requirements as a domain service so they are available to both the web and web service implementations.

Use an authorisation filter to perform your authorisation checks within the web application, this should be as simple as creating an auth request object and then passing it to your auth domain service.

If the authorisation fails, return the correct error - a 401 as indicated by Craig Stuntz.

ALWAYS authorise the action. If you can hide the link to unauthorised users - thats nice.

Simplify your views / view logic by writing a HtmlHelper extension method that can show / hide things based on a call to the auth domain service.

To use your authorisation service from the web service is simply a matter of constructing the auth request object from something passed in via the service message instead of from a cookie passed by the users browser.

Neal