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();
}
Generally spoken every method in my Repository should consider the UserId in the CRUD action.
What do you think?
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.
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.
This is an old question, but for anyone meeting a similar issue, here's a possible solution.
public interface IService {
HasUserPermission(int32 userIdAuthenticated, int targetId));}
public interface ISchoolyearService : IService {
/* Include all other methods except for HasUserPermission */
}
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 😊
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With