Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 568
Elisabeth Avatar asked Jun 13 '15 09:06

Elisabeth


People also ask

How did you secure your Web API?

Securing your API against the attacks outlined above should be based on: Authentication – Determining the identity of an end user. In a REST API, basic authentication can be implemented using the TLS protocol, but OAuth 2 and OpenID Connect are more secure alternatives.

How do I add authentication to Web API?

In IIS Manager, go to Features View, select Authentication, and enable Basic authentication. In your Web API project, add the [Authorize] attribute for any controller actions that need authentication. A client authenticates itself by setting the Authorization header in the request.


1 Answers

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 😊

like image 165
Ken Avatar answered Sep 29 '22 11:09

Ken