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

3

19

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?

Tibbetts answered 13/6, 2015 at 9:56 Comment(7)
Why a -1 downvote? Dare to write a comment instead?Tibbetts
This question makes me nervous. Folk here - on the whole - aren't security experts. Since you mention bank accounts in your question, I'm assuming this is highly confidential information being accessed. As such, you really need to have a proper understanding of what you are doing here and how to ensure it operates in the securest way possible.Asking questions on SO does not seem the best way of achieving this.Snakebird
@David Your interpretation is wrong. Bank account is just a sample.Tibbetts
@Ewan Of course I can get the userEmail/UserId claim from the bearer token. How can I check for each individual/unique request, that the userId belongs to a certain resource?Tibbetts
You need to explain what the specific difficulty you are having is. Whats wrong with bankAccount.BelongsToUserId for example?Alyciaalyda
That would mean I have to put in every SQL Table a field UserId. I have never seen such a design. So I think there must be another approach.Tibbetts
If you don't have data that defines who "owns" each account, how do you expect to authorize access? Something in your data model must define who owns each account. You must authenticate your users, so you know who is making the request. Finally, you would implement an authorization filter that would check the authenticated user before returning any data. - Just posted an answer with a link.Euterpe
S
1

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 😊

Scalpel answered 18/2, 2019 at 9:59 Comment(0)
E
0

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.

Euterpe answered 13/6, 2015 at 13:20 Comment(0)
H
0

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.

Highhanded answered 22/2, 2019 at 9:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.