简体   繁体   中英

MVC C#: Authenticating current user has rights to view/update data

Our company is currently remaking our software from web forms to MVC and it is currently in alpha stage before we release it to our clients. In a nutshell, the app have multiple hotels subscribing and using our service. The core security I now need to ensure is that no user should be able to access/modify data belonging to another hotel.

Our models mostly follow either one of the two examples below:

RoomCategory   (structure: model.HotelID)
--------------
ID
HotelID

Room    (structure: model.Parent.HotelID)
--------------
ID
RoomCategoryID
HotelID

The problem is that when I am on ~/rooms/edit/1, I can change the form action URL and the hidden field value from '1' to '50' and room ID 50 belongs to another hotel. This is a big problem since one user can actually 'steal' a room from another hotel and make it their own! Our clients will not be very happy.

The way I'm approaching the problem...

From the currently logged-in user data (accessed from session), we know the Hotel (or Hotels) the user has the rights to manage. One way is to authenticate each action call and do something like this:

app.AuthenticateAccess(room.RoomCategory.HotelID);

This way, the AuthenticateAccess function will prevent further operation and 'redirect' to an Unauthorized/NotFound page since it knows that Room ID 50 belongs to HotelID 2 while the current user doesn't have access to it. Sure I think this is a safe way, but this involves a lot of duplicate function calls across all actions in each controller.

I have been looking into different possibilities to overcome this security challenge at a global level:

  1. Easiest. Encrypt all IDs and hidden fields value though I personally think that even without encryption, the system should be able to authenticate the access each user has.
  2. Set HotelID in a separate abstract class and let each model (which stores data about one particular hotel) inherits from this abstract class. Perhaps some kind of generics can be done here?
  3. We use repository/unit of work pattern. Is there some way to limit the data which we can retrieve/update to that of the currently selected Hotel ID only?
  4. [Your awesome solution goes here.]

I have some other solutions in mind like using System.Reflection to search for all HotelID attributes and ensuring all the data saved/created is allowed for the current user to make. But anyway let me hear your approach to solve this issue since I am not convinced yet with any solution that I can think of.

Never trust the data coming from client. Always validate the posted data before doing any updates/transaction on your database.

So in your HttpPost action method, You run a sanitization check before proceeding

[HttpPost]
public ActionResult Book(BookingViewModel model)
{
  var isGood = hotelService.DoesCurrentUserHasAccessToRoom(model.RoomId);
  if(isGood)
  {
     //Continue with Saving
     hotelService.BookRoom(model);
     return RedirectToAction("BookingCompleted");
  }
  return View("NotAuthorized");
}

There won't be duplicate code as long as you put them into small reusable methods in your service as needed.

Other option would be to add HMAC field to check sensitive data (hidden id field) has not been changed, I use it once and never use it again, overkill solution.

That being said...

I have run into the same issue multiple times, and I thought almost the same options, in the long run I stick with simplest solution the GetById repository method passing the CurrentUserId , something like:

Entity GetById(int id, int? userId);

Why?

You are asking a granular level of authorization, which can be obtained only when you know the exact record involved in the operation, so why not check for that in the method responsible for obtaining the record in the first place?

If the record exist and the user created that record then return the record, else return null as the object does not exist for that user, don't even bother returning a "You don't own that object" type of message just a plain and simple 404 "the record does not exist".

If you want you can try this same approach but with ActionFilters , something similar to this . But you must figure out how to retrieve the record in that filtercontext.

For admins with full access you can just bypass that check if you pass null to that method.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM