How to protect a Web API from data retrieval not from the resource owner

2.5k views Asked by At

I have an asp.net web api.

I want to own selfhost my Web API later on an azure website.

A logged in user could do this in the browser /api/bankaccounts/3

to get all details about bank account number 3.

But the logged in user is not the owner of bank account number 3.

How do I have to design my Controllers and the services behind that the logged

in user can only retrieve/modify his own resources in the database?

UPDATE

After I created a:

public class UserActionsAuthorizationFilter : AuthorizationFilterAttribute
{
   public override void OnAuthorization(HttpActionContext actionContext)
   {
       if (actionContext != null)
       { 
           bool canUserExecuteAction = IsResourceOwner(actionContext);
           // stop propagation  
       }
   }

private bool IsResourceOwner(HttpActionContext actionContext)
        {
            var principal = (ClaimsPrincipal)Thread.CurrentPrincipal; 
            var userIdAuthenticated = Convert.ToInt32(principal.Claims.Single(c => c.Type == ClaimTypes.Sid).Value);

            int targetId = Convert.ToInt32(actionContext.Request.GetRouteData().Values["Id"]);
            var requstScope = actionContext.ControllerContext.Request.GetDependencyScope();
            var service = (ISchoolyearService)requstScope.GetService(typeof(ISchoolyearService));
            bool canUserExecuteAction = service.HasUserPermission(userIdAuthenticated, targetId);
            return canUserExecuteAction;
        }
}

The question is now that the IsResouceOwner is hardcoded to a certain service => SchoolyearService thus bound to the Schoolyear SQL table

I need to keep the IsResourceOwner method generically working for all sql tables having a field UserId/UserEmail.

The problem is -and I really think nobody is doing that this way- that I have to map each Resource owner check to the correct Sql table in the HasUserPermission method.

How should that mapping look like?

Check Controller name "SchoolyearController" thus the table to check is the "schoolyear" table? thats ridiculous.

This custom attribute "UserActionsAuthorizationFilter" will be on every "Data" controller.

Whatever controller url the user triggers to fetch data, before I have to check wether he is resource owner.

I guess I can not decide this inside a filter.

I have to let the data retrieval/modification go through the controller and do the ResourceOwner check inside maybe in a Repository just before the data retrieval is done.

What do you think of this:

API

public async Task<IHttpActionResult> Delete(int id)
{
   var result = await service.Delete(id, User.Identity.UserId);
    if (result == 0)
        return NotFound();
    return Ok();
}

REPO

    public async Task<int> Delete(int id, int userId)
    {
        var schoolyerToDelete = await context.Schoolyears.SingleOrDefaultAsync(s => s.Id == id && s.UserId == userId); 

// If schoolyearToDelete is null nothing is removed, thus the affected rows are ZERO.
        context.Schoolyears.Remove(schoolyerToDelete);
       return await context.SaveChangesAsync();
    }
  • For the Get Method nothing is returned for the wrong UserId
  • For the Create Method: no problem, everyone should be able to create a resource if logged in.
  • For the Update Method: same as Delete method the schoolyear is retrieved by id and UserId.

Generally spoken every method in my Repository should consider the UserId in the CRUD action.

What do you think?

3

There are 3 answers

0
snow_FFFFFF On

See the following link - it covers both Authentication (so you know who is requesting) and Authorization (so you know if they are authorized to see the data):

http://www.asp.net/web-api/overview/security/authentication-and-authorization-in-aspnet-web-api

To add some other detail - it would be very common to have columns and/or tables in your database that define the authorization of users. It is also possible (depending on your authentication mechanism), that the authentication provider might be providing "claims" or other information that define what the user is authorized to access. However, this could potentially be less secure as you will really need to trust the source of this information and have a way to ensure that it hadn't been tampered with prior to being submitted to your api.

0
Ken On

This is an old question, but for anyone meeting a similar issue, here's a possible solution.

Add a layer of abstraction

  • You can make do by using UserActionsAuthorizationFilter as it was before; just do the following
  • Make all your service interfaces (like ISchoolyearService) inherit a common interface that defines HasUserPermission
public interface IService {
  HasUserPermission(int32 userIdAuthenticated, int targetId));}

public interface ISchoolyearService : IService {
  /* Include all other methods except for HasUserPermission */
}
  • In UserActionsAuthorizationFilter, add a field: "internal IService ServiceProvider"
  • In UserActionsAuthorizationFilter, modify IsResourceOwner():

from

var service = (ISchoolyearService)requstScope.GetService(typeof(ISchoolyearService));

to

var service = (IService)requstScope.GetService(this.ServiceProvider);
- Then, change all the attributes on your Controllers to specify which type of IService they use
[UserActionsAuthorizationFilter(ServiceProvider = typeof(ISchoolyearService))] <br> internal SchoolyearController : Controller { }

The notable drawback of this approach is that you're then committed to only allowing users to access a resource if they pass the HasUserPermission() check, so this way you're barred from making any deeper URLs that should be publically accessible, like /api/testresults/3/public

P.S> You are right that it's ridiculous to figure out which SQL table to check based on the controller name

0
Amir Molaei On

I agree with doing that in repository. That's what I normally do in the scenarios that each record has an owner and only the owner is permitted to modify or delete that record.
Therefore, each record needs to be stored with a foreign key to its owner.
Also, it cannot be handled with authorization and role assignment since you said that a single user is permitted to modify its data not a group of users.